1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Name:         mei/base.py
4# Purpose:      Public methods for the MEI module
5#
6# Authors:      Christopher Antila
7#
8# Copyright:    Copyright © 2014 Michael Scott Cuthbert and the music21 Project
9# License:      BSD, see license.txt
10# -----------------------------------------------------------------------------
11'''
12These are the public methods for the MEI module by Christopher Antila
13
14To convert a string with MEI markup into music21 objects,
15use :meth:`~music21.mei.MeiToM21Converter.convertFromString`.
16
17In the future, most of the functions in this module should be moved to a separate, import-only
18module, so that functions for writing music21-to-MEI will fit nicely.
19
20**Simple "How-To"**
21
22Use :class:`MeiToM21Converter` to convert a string to a set of music21 objects. In the future, the
23:class:`M21ToMeiConverter` class will convert a set of music21 objects into a string with an MEI
24document.
25
26>>> meiString = """<?xml version="1.0" encoding="UTF-8"?>
27... <mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="2013">
28...     <music>
29...     <score>
30...         <scoreDef meter.count="6" meter.unit="8">
31...             <staffGrp>
32...                 <staffDef n="1" clef.shape="F" clef.line="4"/>
33...             </staffGrp>
34...         </scoreDef>
35...         <section>
36...             <scoreDef key.sig="1f" key.mode="major"/>
37...             <measure n="1">
38...                 <staff n="1">
39...                     <layer n="1">
40...                         <beam>
41...                             <note pname="E" oct="3" dur="8" artic="stacc"/>
42...                             <note pname="E" oct="3" dur="8"/>
43...                             <note pname="E" oct="3" dur="8"/>
44...                         </beam>
45...                         <chord dur="4" dots="1">
46...                             <note pname="F" oct="2"/>
47...                             <note pname="A" oct="2" accid="f"/>
48...                         </chord>
49...                     </layer>
50...                 </staff>
51...             </measure>
52...         </section>
53... </score>
54...     </music>
55... </mei>
56... """
57>>> from music21 import *
58>>> conv = mei.MeiToM21Converter(meiString)
59>>> result = conv.run()
60>>> result
61<music21.stream.Score 0x10ee474f0>
62
63**Terminology**
64
65This module's documentation adheres to the following terminology regarding XML documents, using
66this snippet, ``<note pname="C"/>`` as an example:
67
68- the entire snippet is an *element*.
69- the word ``note`` is the *tag*.
70- the word ``pname`` is an *attribute*.
71- the letter ``C`` is a *value*.
72
73Because Python also uses "attributes," an XML attribute is always preceded by an "at sign," as in
74@pname, whereas a Python attribute is set as :attr:`pname`.
75
76**Ignored Elements**
77
78The following elements are not yet imported, though you might expect they would be:
79
80* <sb>: a system break, since this is not usually semantically significant
81* <lb>: a line break, since this is not usually semantically significant
82* <pb>: a page break, since this is not usually semantically significant
83
84**Where Elements Are Processed**
85
86Most elements are processed in functions called :func:`tagFromElement`, where "tag" is replaced by
87the element's tag name (e.g., :func:`staffDefFromElement` for <staffDef> elements). These functions
88convert from a Python :class:`xml.etree.ElementTree.Element`
89object to the appropriate music21 object.
90
91However, certain elements are processed primarily in
92another way, by "private" functions that are not
93documented in this API. Rather than converting an :class:`Element` object into a music21 object,
94these functions modify the MEI document tree by adding instructions for the :func:`tagFromElement`
95functions. The elements processed by private functions include:
96
97* <slur>
98* <tie>
99* <beamSpan>
100* <tupletSpan>
101
102Whereas you can expect functions like :func:`clefFromElement`
103to convert a <clef> into a :class:`Clef`
104with no loss of information. Because we cannot provide a simple one-to-one conversion for  slurs,
105ties, and tuplets, we have kept their conversion functions "private,"
106to emphasize the fact that you
107must use the :class:`MeiToM21Converter` to process them properly.
108
109**Guidelines for Encoders**
110
111While we aim for the best possible compatibility, the MEI
112specification is very large. The following
113guidelines will help you produce a file that this MEI-to-music21 module will import correctly and
114in the most efficient way. These should not necessarily be considered recommendations when using
115MEI in any other context.
116
117* Tuplets indicated only in a @tuplet attribute do not work.
118* For elements that allow @startid, @endid, and @plist attributes,
119  use all three for faster importing.
120* For a <tupletSpan> that does not specify a @plist attribute, a tuplet spanning more than two
121  measures will always and unavoidably be imported incorrectly.
122* For any tuplet, specify at least @num and @numbase. The module refuses to import a tuplet that
123  does not have the @numbase attribute.
124* Retain consistent @n values for the same layer, staff, and instrument throughout the score.
125* Always indicate the duration of <mRest> and <mSpace> elements.
126* Avoid using the <barLine> element if you require well-formatted output from music21, since (as of
127  January 2015) the music21-to-something converters will only output a :class:`Barline` that is
128  part of a :class:`Measure`.
129
130**List of Supported Elements**
131
132Alphabetical list of the elements currently supported by this module:
133
134* :func:`accidFromElement`
135* :func:`articFromElement`
136* :func:`barLineFromElement`
137* :func:`beamFromElement`
138* :func:`chordFromElement`
139* :func:`clefFromElement`
140* :func:`dotFromElement`
141* :func:`instrDefFromElement`
142* :func:`layerFromElement`
143* :func:`measureFromElement`
144* :func:`noteFromElement`
145* :func:`restFromElement`
146* :func:`mRestFromElement`
147* :func:`spaceFromElement`
148* :func:`mSpaceFromElement`
149* :func:`scoreFromElement`
150* :func:`scoreDefFromElement`
151* :func:`sectionFromElement`
152* :func:`staffFromElement`
153* :func:`staffDefFromElement`
154* :func:`staffGrpFromElement`
155* :func:`sylFromElement`
156* :func:`tupletFromElement`
157* :func:`verseFromElement`
158
159To know which MEI attributes are known to import correctly, read the documentation for the relevant
160element. For example, to know whether the @color attribute on a <note> element is supported, read
161the "Attributes/Elements Implemented" section of the :func:`noteFromElement` documentation.
162
163**List of Ignored Elements**
164
165The following elements are (silently) ignored by the MEI-to-music21 converter because they primarily
166affect the layout and typesetting of a musical score. We may choose to implement these elements in
167the future, but they are a lower priority because music21 is not primarily a layout or typesetting
168tool.
169
170* <multiRest>: a multi-measure rest (these will be "converted" to single-measure rests)
171* <pb>: a page break
172* <lb>: a line break
173* <sb>: a system break
174
175'''
176# pylint: disable=misplaced-comparison-constant
177from typing import Optional, Union, List, Tuple
178from xml.etree.ElementTree import Element, ParseError, fromstring, ElementTree
179
180from collections import defaultdict
181from fractions import Fraction  # for typing
182from uuid import uuid4
183
184# music21
185from music21 import articulations
186from music21 import bar
187from music21 import chord
188from music21 import clef
189from music21 import duration
190from music21 import environment
191from music21 import exceptions21
192from music21 import instrument
193from music21 import interval
194from music21 import key
195from music21 import metadata
196from music21 import meter
197from music21 import note
198from music21 import pitch
199from music21 import stream
200from music21 import spanner
201from music21 import tie
202
203_MOD = 'mei.base'
204environLocal = environment.Environment(_MOD)
205
206
207# Module-Level Constants
208# -----------------------------------------------------------------------------
209_XMLID = '{http://www.w3.org/XML/1998/namespace}id'
210MEI_NS = '{http://www.music-encoding.org/ns/mei}'
211# when these tags aren't processed, we won't worry about them (at least for now)
212_IGNORE_UNPROCESSED = (
213    f'{MEI_NS}sb',  # system break
214    f'{MEI_NS}lb',  # line break
215    f'{MEI_NS}pb',  # page break
216    f'{MEI_NS}slur',  # slurs; handled in convertFromString()
217    f'{MEI_NS}tie',  # ties; handled in convertFromString()
218    f'{MEI_NS}tupletSpan',  # tuplets; handled in convertFromString()
219    f'{MEI_NS}beamSpan',  # beams; handled in convertFromString()
220    f'{MEI_NS}instrDef',  # instrument; handled separately by staffDefFromElement()
221)
222
223
224# Exceptions
225# -----------------------------------------------------------------------------
226class MeiValidityError(exceptions21.Music21Exception):
227    'When there is an otherwise-unspecified validity error that prevents parsing.'
228    pass
229
230
231class MeiValueError(exceptions21.Music21Exception):
232    'When an attribute has an invalid value.'
233    pass
234
235
236class MeiAttributeError(exceptions21.Music21Exception):
237    'When an element has an invalid attribute.'
238    pass
239
240
241class MeiElementError(exceptions21.Music21Exception):
242    'When an element itself is invalid.'
243    pass
244
245
246# Text Strings for Error Conditions
247# -----------------------------------------------------------------------------
248# NOTE: these are all collected handily at the top for two reasons: help you find the easier, and
249#       help you translate them easier
250_TEST_FAILS = 'MEI module had {} failures and {} errors; run music21/mei/base.py to find out more.'
251_INVALID_XML_DOC = 'MEI document is not valid XML.'
252_WRONG_ROOT_ELEMENT = 'Root element should be <mei> in the MEI namespace, not <{}>.'
253_UNKNOWN_TAG = 'Found unexpected tag while parsing MEI: <{}>.'
254_UNEXPECTED_ATTR_VALUE = 'Unexpected value for "{}" attribute: {}'
255_SEEMINGLY_NO_PARTS = 'There appear to be no <staffDef> tags in this score.'
256_MISSING_VOICE_ID = 'Found a <layer> without @n attribute and no override.'
257_CANNOT_FIND_XMLID = 'Could not find the @{} so we could not create the {}.'
258_MISSING_TUPLET_DATA = 'Both @num and @numbase attributes are required on <tuplet> tags.'
259_UNIMPLEMENTED_IMPORT = 'Importing {} without {} is not yet supported.'
260_UNPROCESSED_SUBELEMENT = 'Found an unprocessed <{}> element in a <{}>.'
261_MISSED_DATE = 'Unable to decipher the composition date "{}"'
262_BAD_VERSE_NUMBER = 'Verse number must be an int (got "{}")'
263
264
265# Module-level Functions
266# -----------------------------------------------------------------------------
267class MeiToM21Converter:
268    '''
269    A :class:`MeiToM21Converter` instance manages the conversion of an MEI document into music21
270    objects.
271
272    If ``theDocument`` does not have <mei> as the root element, the class raises an
273    :class:`MeiElementError`. If ``theDocument`` is not a valid XML file, the class raises an
274    :class:`MeiValidityError`.
275
276    :param str theDocument: A string containing an MEI document.
277    :raises: :exc:`MeiElementError` when the root element is not <mei>
278    :raises: :exc:`MeiValidityError` when the MEI file is not valid XML.
279    '''
280
281    def __init__(self, theDocument=None):
282        #  The __init__() documentation doesn't isn't processed by Sphinx,
283        #  so I put it at class level.
284        environLocal.printDebug('*** initializing MeiToM21Converter')
285
286        if theDocument is None:
287            # Without this, the class can't be pickled.
288            self.documentRoot = Element(f'{MEI_NS}mei')
289        else:
290            try:
291                self.documentRoot = fromstring(theDocument)
292            except ParseError as parseErr:
293                environLocal.printDebug(
294                    '\n\nERROR: Parsing the MEI document with ElementTree failed.')
295                environLocal.printDebug(f'We got the following error:\n{parseErr}')
296                raise MeiValidityError(_INVALID_XML_DOC)
297
298            if isinstance(self.documentRoot, ElementTree):
299                # pylint warns that :class:`Element` doesn't have a getroot() method, which is
300                # true enough, but...
301                self.documentRoot = self.documentRoot.getroot()  # pylint: disable=maybe-no-member
302
303            if f'{MEI_NS}mei' != self.documentRoot.tag:
304                raise MeiElementError(_WRONG_ROOT_ELEMENT.format(self.documentRoot.tag))
305
306        # This defaultdict stores extra, music21-specific attributes that we add to elements to help
307        # importing. The key is an element's @xml:id, and the value is a regular dict with keys
308        # corresponding to attributes we'll add and values
309        # corresponding to those attributes' values.
310        self.m21Attr = defaultdict(lambda: {})
311
312        # This SpannerBundle holds the slurs that will be created by _ppSlurs() and used while
313        # importing whatever note, rest, chord, or other object.
314        self.slurBundle = spanner.SpannerBundle()
315
316    def run(self) -> stream.Stream:
317        '''
318        Run conversion of the internal MEI document to produce a music21 object.
319
320        Returns a :class:`~music21.stream.Stream` subclass, depending on the MEI document.
321        '''
322
323        environLocal.printDebug('*** pre-processing spanning elements')
324        _ppSlurs(self)
325        _ppTies(self)
326        _ppBeams(self)
327        _ppTuplets(self)
328        _ppConclude(self)
329
330        environLocal.printDebug('*** processing <score> elements')
331        theScore = scoreFromElement(
332            self.documentRoot.find(f'.//{MEI_NS}music//{MEI_NS}score'),
333            self.slurBundle)
334
335        environLocal.printDebug('*** preparing metadata')
336        theScore.metadata = makeMetadata(self.documentRoot)
337
338        return theScore
339
340
341# Module-level Functions
342# -----------------------------------------------------------------------------
343def safePitch(
344    name: str,
345    accidental: Optional[str] = None,
346    octave: Union[str, int] = ''
347) -> pitch.Pitch:
348    '''
349    Safely build a :class:`~music21.pitch.Pitch` from a string.
350
351    When :meth:`~music21.pitch.Pitch.__init__` is given an empty string,
352    it raises a :exc:`~music21.pitch.PitchException`. This
353    function instead returns a default :class:`~music21.pitch.Pitch` instance.
354
355    name: Desired name of the :class:`~music21.pitch.Pitch`.
356
357    accidental: (Optional) Symbol for the accidental.
358
359    octave: (Optional) Octave number.
360
361    Returns A :class:`~music21.pitch.Pitch` with the appropriate properties.
362
363    >>> from music21.mei.base import safePitch  # OMIT_FROM_DOCS
364    >>> safePitch('D#6')
365    <music21.pitch.Pitch D#6>
366    >>> safePitch('D', '#', '6')
367    <music21.pitch.Pitch D#6>
368    '''
369    if not name:
370        return pitch.Pitch()
371    elif accidental is None:
372        return pitch.Pitch(name + octave)
373    else:
374        return pitch.Pitch(name, accidental=accidental, octave=int(octave))
375
376
377def makeDuration(
378    base: Union[float, int, Fraction] = 0.0,
379    dots: int = 0
380) -> 'music21.duration.Duration':
381    '''
382    Given a ``base`` duration and a number of ``dots``, create a :class:`~music21.duration.Duration`
383    instance with the
384    appropriate ``quarterLength`` value.
385
386    Returns a :class:`Duration` corresponding to the fully-augmented value.
387
388    **Examples**
389
390    >>> from music21 import *
391    >>> from fractions import Fraction
392    >>> mei.base.makeDuration(base=2.0, dots=0).quarterLength  # half note, no dots
393    2.0
394    >>> mei.base.makeDuration(base=2.0, dots=1).quarterLength  # half note, one dot
395    3.0
396    >>> mei.base.makeDuration(base=2, dots=2).quarterLength  # 'base' can be an int or float
397    3.5
398    >>> mei.base.makeDuration(2.0, 10).quarterLength  # you want ridiculous dots? Sure...
399    3.998046875
400    >>> mei.base.makeDuration(0.33333333333333333333, 0).quarterLength  # works with fractions too
401    Fraction(1, 3)
402    >>> mei.base.makeDuration(Fraction(1, 3), 1).quarterLength
403    0.5
404    '''
405    returnDuration = duration.Duration(base)
406    returnDuration.dots = dots  # pylint: disable=assigning-non-slot
407    return returnDuration
408
409
410def allPartsPresent(scoreElem) -> Tuple[str, ...]:
411    # noinspection PyShadowingNames
412    '''
413    Find the @n values for all <staffDef> elements in a <score> element. This assumes that every
414    MEI <staff> corresponds to a music21 :class:`~music21.stream.Part`.
415
416    scoreElem is the <score> `Element` in which to find the part names.
417    Returns all the unique @n values associated with a part in the <score>.
418
419    **Example**
420
421    >>> meiDoc = """<?xml version="1.0" encoding="UTF-8"?>
422    ... <score xmlns="http://www.music-encoding.org/ns/mei">
423    ...     <scoreDef>
424    ...         <staffGrp>
425    ...             <staffDef n="1" clef.shape="G" clef.line="2"/>
426    ...             <staffDef n="2" clef.shape="F" clef.line="4"/>
427    ...         </staffGrp>
428    ...     </scoreDef>
429    ...     <section>
430    ...         <!-- ... some music ... -->
431    ...         <staffDef n="2" clef.shape="C" clef.line="4"/>
432    ...         <!-- ... some music ... -->
433    ...     </section>
434    ... </score>"""
435    >>> import xml.etree.ElementTree as ETree
436    >>> from music21 import *
437    >>> meiDoc = ETree.fromstring(meiDoc)
438    >>> mei.base.allPartsPresent(meiDoc)
439    ('1', '2')
440
441    Even though there are three <staffDef> elements in the document, there are only two unique @n
442    attributes. The second appearance of <staffDef> with @n="2" signals a change of clef on that
443    same staff---not that there is a new staff.
444    '''
445    # xpathQuery = f'.//{MEI_NS}music//{MEI_NS}score//{MEI_NS}staffDef'
446    xpathQuery = f'.//{MEI_NS}staffDef'
447    partNs = []  # hold the @n attribute for all the parts
448
449    for staffDef in scoreElem.findall(xpathQuery):
450        if staffDef.get('n') not in partNs:
451            partNs.append(staffDef.get('n'))
452    if not partNs:
453        raise MeiValidityError(_SEEMINGLY_NO_PARTS)
454    return tuple(partNs)
455
456
457# Constants for One-to-One Translation
458# -----------------------------------------------------------------------------
459# for _accidentalFromAttr()
460# None is for when @accid is omitted
461_ACCID_ATTR_DICT = {'s': '#', 'f': '-', 'ss': '##', 'x': '##', 'ff': '--', 'xs': '###',
462                    'ts': '###', 'tf': '---', 'n': 'n', 'nf': '-', 'ns': '#', 'su': '#~',
463                    'sd': '~', 'fu': '`', 'fd': '-`', 'nu': '~', 'nd': '`', None: None}
464
465# for _accidGesFromAttr()
466# None is for when @accid is omitted
467_ACCID_GES_ATTR_DICT = {'s': '#', 'f': '-', 'ss': '##', 'ff': '--', 'n': 'n', 'su': '#~',
468                        'sd': '~', 'fu': '`', 'fd': '-`', None: None}
469
470# for _qlDurationFromAttr()
471# None is for when @dur is omitted; it's silly so it can be identified
472_DUR_ATTR_DICT = {'long': 16.0, 'breve': 8.0, '1': 4.0, '2': 2.0, '4': 1.0, '8': 0.5, '16': 0.25,
473                  '32': 0.125, '64': 0.0625, '128': 0.03125, '256': 0.015625, '512': 0.0078125,
474                  '1024': 0.00390625, '2048': 0.001953125, None: 0.00390625}
475
476# for _articulationFromAttr()
477# NOTE: 'marc-stacc' and 'ten-stacc' require multiple music21 events, so they are handled
478#       separately in _articulationFromAttr().
479_ARTIC_ATTR_DICT = {'acc': articulations.Accent,
480                    'stacc': articulations.Staccato,
481                    'ten': articulations.Tenuto,
482                    'stacciss': articulations.Staccatissimo,
483                    'marc': articulations.StrongAccent,
484                    'spicc': articulations.Spiccato,
485                    'doit': articulations.Doit,
486                    'plop': articulations.Plop,
487                    'fall': articulations.Falloff,
488                    'dnbow': articulations.DownBow,
489                    'upbow': articulations.UpBow,
490                    'harm': articulations.Harmonic,
491                    'snap': articulations.SnapPizzicato,
492                    'stop': articulations.Stopped,
493                    'open': articulations.OpenString,  # this may also mean "no mute?"
494                    'dbltongue': articulations.DoubleTongue,
495                    'toe': articulations.OrganToe,
496                    'trpltongue': articulations.TripleTongue,
497                    'heel': articulations.OrganHeel,
498                    # TODO: these aren't implemented in music21, so I'll make new ones
499                    'tap': articulations.Articulation,
500                    'lhpizz': articulations.Articulation,
501                    'dot': articulations.Articulation,
502                    'stroke': articulations.Articulation,
503                    'rip': articulations.Articulation,
504                    'bend': articulations.Articulation,
505                    'flip': articulations.Articulation,
506                    'smear': articulations.Articulation,
507                    'fingernail': articulations.Articulation,  # (u1D1B3)
508                    'damp': articulations.Articulation,
509                    'dampall': articulations.Articulation,
510                    }
511
512# for _barlineFromAttr()
513# TODO: make new music21 Barline styles for 'dbldashed' and 'dbldotted'
514_BAR_ATTR_DICT = {'dashed': 'dashed',
515                  'dotted': 'dotted',
516                  'dbl': 'double',
517                  'end': 'final',
518                  'invis': 'none',
519                  'single': 'regular',
520                  }
521
522
523# One-to-One Translator Functions
524# -----------------------------------------------------------------------------
525def _attrTranslator(attr, name, mapping):
526    '''
527    Helper function for other functions that need to translate the value of an attribute to another
528    known value. :func:`_attrTranslator` tries to return the value of ``attr`` in ``mapping`` and,
529    if ``attr`` isn't in ``mapping``, an exception is raised.
530
531    :param str attr: The value of the attribute to look up in ``mapping``.
532    :param str name: Name of the attribute, used when raising an exception (read below).
533    :param mapping: A mapping type (nominally a dict) with relevant key-value pairs.
534
535    :raises: :exc:`MeiValueError` when ``attr`` is not found in ``mapping``. The error message will
536        be of this format: 'Unexpected value for "name" attribute: attr'.
537
538    Examples:
539
540    >>> from music21.mei.base import _attrTranslator, _ACCID_ATTR_DICT, _DUR_ATTR_DICT
541    >>> _attrTranslator('s', 'accid', _ACCID_ATTR_DICT)
542    '#'
543    >>> _attrTranslator('9', 'dur', _DUR_ATTR_DICT)
544    Traceback (most recent call last):
545    music21.mei.base.MeiValueError: Unexpected value for "dur" attribute: 9
546    '''
547    try:
548        return mapping[attr]
549    except KeyError:
550        raise MeiValueError(_UNEXPECTED_ATTR_VALUE.format(name, attr))
551
552
553def _accidentalFromAttr(attr):
554    '''
555    Use :func:`_attrTranslator` to convert the value of an "accid" attribute to its music21 string.
556
557    >>> from music21 import *
558    >>> mei.base._accidentalFromAttr('s')
559    '#'
560    '''
561    return _attrTranslator(attr, 'accid', _ACCID_ATTR_DICT)
562
563
564def _accidGesFromAttr(attr):
565    '''
566    Use :func:`_attrTranslator` to convert the value of an @accid.ges
567    attribute to its music21 string.
568
569    >>> from music21 import *
570    >>> mei.base._accidGesFromAttr('s')
571    '#'
572    '''
573    return _attrTranslator(attr, 'accid.ges', _ACCID_GES_ATTR_DICT)
574
575
576def _qlDurationFromAttr(attr):
577    '''
578    Use :func:`_attrTranslator` to convert an MEI "dur" attribute to a music21 quarterLength.
579
580    >>> from music21 import *
581    >>> mei.base._qlDurationFromAttr('4')
582    1.0
583
584    .. note:: This function only handles data.DURATION.cmn, not data.DURATION.mensural.
585    '''
586    return _attrTranslator(attr, 'dur', _DUR_ATTR_DICT)
587
588
589def _articulationFromAttr(attr):
590    '''
591    Use :func:`_attrTranslator` to convert an MEI "artic" attribute to a
592    :class:`music21.articulations.Articulation` subclass.
593
594    :returns: A **tuple** of one or two :class:`Articulation` subclasses.
595
596    .. note:: This function returns a singleton tuple *unless* ``attr`` is ``'marc-stacc'`` or
597        ``'ten-stacc'``. These return ``(StrongAccent, Staccato)`` and ``(Tenuto, Staccato)``,
598        respectively.
599    '''
600    if 'marc-stacc' == attr:
601        return (articulations.StrongAccent(), articulations.Staccato())
602    elif 'ten-stacc' == attr:
603        return (articulations.Tenuto(), articulations.Staccato())
604    else:
605        return (_attrTranslator(attr, 'artic', _ARTIC_ATTR_DICT)(),)
606
607
608def _makeArticList(attr):
609    '''
610    Use :func:`_articulationFromAttr` to convert the actual value of an MEI "artic" attribute
611    (including multiple items) into a list suitable for :attr:`GeneralNote.articulations`.
612    '''
613    articList = []
614    for eachArtic in attr.split(' '):
615        articList.extend(_articulationFromAttr(eachArtic))
616    return articList
617
618
619def _getOctaveShift(dis, disPlace):
620    '''
621    Use :func:`_getOctaveShift` to calculate the :attr:`octaveShift` attribute for a
622    :class:`~music21.clef.Clef` subclass. Any of the arguments may be ``None``.
623
624    :param str dis: The "dis" attribute from the <clef> tag.
625    :param str disPlace: The "dis.place" attribute from the <clef> tag.
626
627    :returns: The octave displacement compared to the clef's normal position. This may be 0.
628    :rtype: integer
629    '''
630    # NB: dis: 8, 15, or 22 (or "ottava" clefs)
631    # NB: dis.place: "above" or "below" depending on whether the ottava clef is Xva or Xvb
632    octavesDict = {None: 0, '8': 1, '15': 2, '22': 3}
633    if 'below' == disPlace:
634        return -1 * octavesDict[dis]
635    else:
636        return octavesDict[dis]
637
638
639def _sharpsFromAttr(signature):
640    '''
641    Use :func:`_sharpsFromAttr` to convert MEI's ``data.KEYSIGNATURE`` datatype to an integer
642    representing the number of sharps, for use with music21's :class:`~music21.key.KeySignature`.
643
644    :param str signature: The @key.sig attribute.
645    :returns: The number of sharps.
646    :rtype: int
647
648    >>> from music21.mei.base import _sharpsFromAttr
649    >>> _sharpsFromAttr('3s')
650    3
651    >>> _sharpsFromAttr('3f')
652    -3
653    >>> _sharpsFromAttr('0')
654    0
655    '''
656    if signature.startswith('0'):
657        return 0
658    elif signature.endswith('s'):
659        return int(signature[0])
660    else:
661        return -1 * int(signature[0])
662
663
664# "Preprocessing" and "Postprocessing" Functions for convertFromString()
665# -----------------------------------------------------------------------------
666def _ppSlurs(theConverter):
667    # noinspection PyShadowingNames
668    '''
669    Pre-processing helper for :func:`convertFromString` that handles slurs specified in <slur>
670    elements. The input is a :class:`MeiToM21Converter` with data about the file currently being
671    processed. This function reads from ``theConverter.documentRoot`` and writes into
672    ``theConverter.m21Attr`` and ``theConverter.slurBundle``.
673
674    :param theConverter: The object responsible for storing data about this import.
675    :type theConverter: :class:`MeiToM21Converter`.
676
677    **This Preprocessor**
678
679    The slur preprocessor adds @m21SlurStart and @m21SlurEnd attributes to elements that are at the
680    beginning or end of a slur. The value of these attributes is the ``idLocal`` of a :class:`Slur`
681    in the :attr:`slurBundle` attribute of ``theConverter``. This attribute is not part of the MEI
682    specification, and must therefore be handled specially.
683
684    If :func:`noteFromElement` encounters an element like ``<note m21SlurStart="82f87cd7"/>``, the
685    resulting :class:`music21.note.Note` should be set as the starting point of the slur with an
686    ``idLocal`` of ``'82f87cd7'``.
687
688    **Example of Changes to ``m21Attr``**
689
690    The ``theConverter.m21Attr`` attribute must be a defaultdict that returns an empty (regular)
691    dict for non-existant keys. The defaultdict stores the @xml:id attribute of an element; the
692    dict holds attribute names and their values that should be added to the element with the
693    given @xml:id.
694
695    For example, if the value of ``m21Attr['fe93129e']['tie']`` is ``'i'``, then this means the
696    element with an @xml:id of ``'fe93129e'`` should have the @tie attribute set to ``'i'``.
697
698    **Example**
699
700    Consider the following example.
701
702    >>> meiDoc = """<?xml version="1.0" encoding="UTF-8"?>
703    ... <mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="2013">
704    ...     <music><score>
705    ...     <section>
706    ...         <note xml:id="1234"/>
707    ...         <note xml:id="2345"/>
708    ...         <slur startid="#1234" endid="#2345"/>
709    ...     </section>
710    ...     </score></music>
711    ... </mei>"""
712    >>> from music21 import *
713    >>> theConverter = mei.base.MeiToM21Converter(meiDoc)
714    >>>
715    >>> mei.base._ppSlurs(theConverter)
716    >>> 'm21SlurStart' in theConverter.m21Attr['1234']
717    True
718    >>> 'm21SlurEnd' in theConverter.m21Attr['2345']
719    True
720    >>> theConverter.slurBundle
721    <music21.spanner.SpannerBundle of size 1>
722    >>> firstSpanner = list(theConverter.slurBundle)[0]
723    >>> (theConverter.m21Attr['1234']['m21SlurStart'] ==
724    ...  theConverter.m21Attr['2345']['m21SlurEnd'] ==
725    ...  firstSpanner.idLocal)
726    True
727
728    This example is a little artificial because of the limitations of a doctest, where we need to
729    know all values in advance. The point here is that the values of 'm21SlurStart' and 'm21SlurEnd'
730    of a particular slur-attached object will match the 'idLocal' of a slur in :attr:`slurBundle`.
731    The "id" is a UUID determined at runtime, which looks something like
732    ``'d3731f89-8a2f-4b82-ad02-f0bc6f5f8b04'``.
733    '''
734    environLocal.printDebug('*** pre-processing slurs')
735    # for readability, we use a single-letter variable
736    c = theConverter  # pylint: disable=invalid-name
737    # pre-processing for <slur> tags
738    for eachSlur in c.documentRoot.iterfind(
739            f'.//{MEI_NS}music//{MEI_NS}score//{MEI_NS}slur'
740    ):
741        if eachSlur.get('startid') is not None and eachSlur.get('endid') is not None:
742            thisIdLocal = str(uuid4())
743            thisSlur = spanner.Slur()
744            thisSlur.idLocal = thisIdLocal
745            c.slurBundle.append(thisSlur)
746
747            c.m21Attr[removeOctothorpe(eachSlur.get('startid'))]['m21SlurStart'] = thisIdLocal
748            c.m21Attr[removeOctothorpe(eachSlur.get('endid'))]['m21SlurEnd'] = thisIdLocal
749        else:
750            environLocal.warn(_UNIMPLEMENTED_IMPORT.format('<slur>', '@startid and @endid'))
751
752
753def _ppTies(theConverter):
754    '''
755    Pre-processing helper for :func:`convertFromString` that handles ties specified in <tie>
756    elements. The input is a :class:`MeiToM21Converter` with data about the file currently being
757    processed. This function reads from ``theConverter.documentRoot`` and writes into
758    ``theConverter.m21Attr``.
759
760    :param theConverter: The object responsible for storing data about this import.
761    :type theConverter: :class:`MeiToM21Converter`.
762
763    **This Preprocessor**
764
765    The tie preprocessor works similarly to the slur preprocessor, adding @tie attributes. The
766    value of these attributes conforms to the MEI Guidelines, so no special action is required.
767
768    **Example of ``m21Attr``**
769
770    The ``theConverter.m21Attr`` attribute must be a defaultdict that returns an empty (regular)
771    dict for non-existent keys. The defaultdict stores the @xml:id attribute of an element; the
772    dict holds attribute names and their values that should be added to the element with the
773    given @xml:id.
774
775    For example, if the value of ``m21Attr['fe93129e']['tie']`` is ``'i'``, then this means the
776    element with an @xml:id of ``'fe93129e'`` should have the @tie attribute set to ``'i'``.
777    '''
778    environLocal.printDebug('*** pre-processing ties')
779    # for readability, we use a single-letter variable
780    c = theConverter  # pylint: disable=invalid-name
781
782    for eachTie in c.documentRoot.iterfind(
783            f'.//{MEI_NS}music//{MEI_NS}score//{MEI_NS}tie'):
784        if eachTie.get('startid') is not None and eachTie.get('endid') is not None:
785            c.m21Attr[removeOctothorpe(eachTie.get('startid'))]['tie'] = 'i'
786            c.m21Attr[removeOctothorpe(eachTie.get('endid'))]['tie'] = 't'
787        else:
788            environLocal.warn(_UNIMPLEMENTED_IMPORT.format('<tie>', '@startid and @endid'))
789
790
791def _ppBeams(theConverter):
792    '''
793    Pre-processing helper for :func:`convertFromString` that handles beams specified in <beamSpan>
794    elements. The input is a :class:`MeiToM21Converter` with data about the file currently being
795    processed. This function reads from ``theConverter.documentRoot`` and writes into
796    ``theConverter.m21Attr``.
797
798    :param theConverter: The object responsible for storing data about this import.
799    :type theConverter: :class:`MeiToM21Converter`.
800
801    **This Preprocessor**
802
803    The beam preprocessor works similarly to the slur preprocessor, adding the @m21Beam attribute.
804    The value of this attribute is either ``'start'``, ``'continue'``, or ``'stop'``, indicating
805    the music21 ``type`` of the primary beam attached to this element. This attribute is not
806    part of the MEI specification, and must therefore be handled specially.
807
808    **Example of ``m21Attr``**
809
810    The ``theConverter.m21Attr`` argument must be a defaultdict that returns an empty (regular)
811    dict for non-existent keys. The defaultdict stores the @xml:id attribute of an element; the
812    dict holds attribute names and their values that should be added to the element with the
813    given @xml:id.
814
815    For example, if the value of ``m21Attr['fe93129e']['tie']`` is ``'i'``, then this means the
816    element with an @xml:id of ``'fe93129e'`` should have the @tie attribute set to ``'i'``.
817    '''
818    environLocal.printDebug('*** pre-processing beams')
819    # for readability, we use a single-letter variable
820    c = theConverter  # pylint: disable=invalid-name
821
822    # pre-processing for <beamSpan> elements
823    for eachBeam in c.documentRoot.iterfind(
824            f'.//{MEI_NS}music//{MEI_NS}score//{MEI_NS}beamSpan'):
825        if eachBeam.get('startid') is None or eachBeam.get('endid') is None:
826            environLocal.warn(_UNIMPLEMENTED_IMPORT.format('<beamSpan>', '@startid and @endid'))
827            continue
828
829        c.m21Attr[removeOctothorpe(eachBeam.get('startid'))]['m21Beam'] = 'start'
830        c.m21Attr[removeOctothorpe(eachBeam.get('endid'))]['m21Beam'] = 'stop'
831
832        # iterate things in the @plist attribute
833        for eachXmlid in eachBeam.get('plist', '').split(' '):
834            eachXmlid = removeOctothorpe(eachXmlid)
835            # if not eachXmlid:
836            #     # this is either @plist not set or extra spaces around the contained xml:id values
837            #     pass
838            if 'm21Beam' not in c.m21Attr[eachXmlid]:
839                # only set to 'continue' if it wasn't previously set to 'start' or 'stop'
840                c.m21Attr[eachXmlid]['m21Beam'] = 'continue'
841
842
843def _ppTuplets(theConverter):
844    '''
845    Pre-processing helper for :func:`convertFromString` that handles tuplets specified in
846    <tupletSpan> elements. The input is a :class:`MeiToM21Converter` with data about the file
847    currently being processed. This function reads from ``theConverter.documentRoot`` and writes
848    into ``theConverter.m21Attr``.
849
850    :param theConverter: The object responsible for storing data about this import.
851    :type theConverter: :class:`MeiToM21Converter`.
852
853    **This Preprocessor**
854
855    The slur preprocessor works similarly to the slur preprocessor, adding @m21TupletNum and
856    @m21TupletNumbase attributes. The value of these attributes corresponds to the @num and
857    @numbase attributes found on a <tuplet> element. This preprocessor also performs a significant
858    amount of guesswork to try to handle <tupletSpan> elements that do not include a @plist
859    attribute. This attribute is not part of the MEI specification, and must therefore be handled
860    specially.
861
862    **Example of ``m21Attr``**
863
864    The ``theConverter.m21Attr`` attribute must be a defaultdict that returns an empty (regular)
865    dict for non-existent keys. The defaultdict stores the @xml:id attribute of an element; the
866    dict holds attribute names and their values that should be added to the element with the
867    given @xml:id.
868
869    For example, if the value of ``m21Attr['fe93129e']['tie']`` is ``'i'``, then this means the
870    element with an @xml:id of ``'fe93129e'`` should have the @tie attribute set to ``'i'``.
871    '''
872    environLocal.printDebug('*** pre-processing tuplets')
873    # for readability, we use a single-letter variable
874    c = theConverter  # pylint: disable=invalid-name
875
876    # pre-processing <tupletSpan> tags
877    for eachTuplet in c.documentRoot.iterfind(
878            f'.//{MEI_NS}music//{MEI_NS}score//{MEI_NS}tupletSpan'):
879        if ((eachTuplet.get('startid') is None or eachTuplet.get('endid') is None)
880                and eachTuplet.get('plist') is None):
881            environLocal.warn(_UNIMPLEMENTED_IMPORT.format('<tupletSpan>',
882                                                           '@startid and @endid or @plist'))
883        elif eachTuplet.get('plist') is not None:
884            # Ideally (for us) <tupletSpan> elements will have a @plist that enumerates the
885            # @xml:id of every affected element. In this case, tupletSpanFromElement() can use the
886            # @plist to add our custom @m21TupletNum and @m21TupletNumbase attributes.
887            for eachXmlid in eachTuplet.get('plist', '').split(' '):
888                eachXmlid = removeOctothorpe(eachXmlid)
889                if eachXmlid:
890                    # protect against extra spaces around the contained xml:id values
891                    c.m21Attr[eachXmlid]['m21TupletNum'] = eachTuplet.get('num')
892                    c.m21Attr[eachXmlid]['m21TupletNumbase'] = eachTuplet.get('numbase')
893        else:
894            # For <tupletSpan> elements that don't give a @plist attribute, we have to do some
895            # guesswork and hope we find all the related elements. Right here, we're only setting
896            # the "flags" that this guesswork must be done later.
897            startid = removeOctothorpe(eachTuplet.get('startid'))
898            endid = removeOctothorpe(eachTuplet.get('endid'))
899
900            c.m21Attr[startid]['m21TupletSearch'] = 'start'
901            c.m21Attr[startid]['m21TupletNum'] = eachTuplet.get('num')
902            c.m21Attr[startid]['m21TupletNumbase'] = eachTuplet.get('numbase')
903            c.m21Attr[endid]['m21TupletSearch'] = 'end'
904            c.m21Attr[endid]['m21TupletNum'] = eachTuplet.get('num')
905            c.m21Attr[endid]['m21TupletNumbase'] = eachTuplet.get('numbase')
906
907
908def _ppConclude(theConverter):
909    '''
910    Pre-processing helper for :func:`convertFromString` that adds attributes from ``m21Attr`` to the
911    appropriate elements in ``documentRoot``. The input is a :class:`MeiToM21Converter` with data
912    about the file currently being processed. This function reads from ``theConverter.m21Attr`` and
913    writes into ``theConverter.documentRoot``.
914
915    :param theConverter: The object responsible for storing data about this import.
916    :type theConverter: :class:`MeiToM21Converter`.
917
918    **Example of ``m21Attr``**
919
920    The ``m21Attr`` argument must be a defaultdict that returns an empty (regular) dict for
921    non-existent keys. The defaultdict stores the @xml:id attribute of an element; the dict holds
922    attribute names and their values that should be added to the element with the given @xml:id.
923
924    For example, if the value of ``m21Attr['fe93129e']['tie']`` is ``'i'``, then this means the
925    element with an @xml:id of ``'fe93129e'`` should have the @tie attribute set to ``'i'``.
926
927    **This Preprocessor**
928    The slur preprocessor adds all attributes from the ``m21Attr`` to the appropriate element in
929    ``documentRoot``. In effect, it finds the element corresponding to each key in ``m21Attr``,
930    then iterates the keys in its dict, *appending* the ``m21Attr``-specified value to any existing
931    value.
932    '''
933    environLocal.printDebug('*** concluding pre-processing')
934    # for readability, we use a single-letter variable
935    c = theConverter  # pylint: disable=invalid-name
936
937    # conclude pre-processing by adding music21-specific attributes to their respective elements
938    for eachObject in c.documentRoot.iterfind('*//*'):
939        # we have a defaultdict, so this "if" isn't strictly necessary; but without it, every single
940        # element with an @xml:id creates a new, empty dict, which would consume a lot of memory
941        if eachObject.get(_XMLID) in c.m21Attr:
942            for eachAttr in c.m21Attr[eachObject.get(_XMLID)]:
943                eachObject.set(eachAttr, (eachObject.get(eachAttr, '')
944                                          + c.m21Attr[eachObject.get(_XMLID)][eachAttr]))
945
946
947# Helper Functions
948# -----------------------------------------------------------------------------
949def _processEmbeddedElements(
950    elements: List[Element],
951    mapping,
952    callerTag=None,
953    slurBundle=None
954):
955    # noinspection PyShadowingNames
956    '''
957    From an iterable of MEI ``elements``, use functions in the ``mapping`` to convert each element
958    to its music21 object. This function was designed for use with elements that may contain other
959    elements; the contained elements will be converted as appropriate.
960
961    If an element itself has embedded elements (i.e., its converter function in ``mapping`` returns
962    a sequence), those elements will appear in the returned sequence in order---there are no
963    hierarchic lists.
964
965    :param elements: A list of :class:`Element` objects to convert to music21 objects.
966    :type elements: iterable of :class:`~xml.etree.ElementTree.Element`
967    :param mapping: A dictionary where keys are the :attr:`Element.tag` attribute and values are
968        the function to call to convert that :class:`Element` to a music21 object.
969    :type mapping: mapping of str to function
970    :param str callerTag: The tag of the element on behalf of which this function is processing
971        sub-elements (e.g., 'note' or 'staffDef'). Do not include < and >. This is used in a
972        warning message on finding an unprocessed element.
973    :param slurBundle: A slur bundle, as used by the other :func:`*fromElements` functions.
974    :type slurBundle: :class:`music21.spanner.SlurBundle`
975    :returns: A list of the music21 objects returned by the converter functions, or an empty list
976        if no objects were returned.
977    :rtype: sequence of :class:`~music21.base.Music21Object`
978
979    **Examples:**
980
981    Because there is no ``'rest'`` key in the ``mapping``, that :class:`Element` is ignored.
982
983    >>> from xml.etree.ElementTree import Element
984    >>> from music21 import *
985    >>> elements = [Element('note'), Element('rest'), Element('note')]
986    >>> mapping = {'note': lambda x, y: note.Note('D2')}
987    >>> mei.base._processEmbeddedElements(elements, mapping, 'doctest')
988    [<music21.note.Note D>, <music21.note.Note D>]
989
990    If debugging is enabled for the previous example, this warning would be displayed:
991
992    ``mei.base: Found an unprocessed <rest> element in a <doctest>.
993
994    The "beam" element holds "note" elements. All elements appear in a single level of the list:
995
996    >>> elements = [Element('note'), Element('beam'), Element('note')]
997    >>> mapping = {'note': lambda x, y: note.Note('D2'),
998    ...            'beam': lambda x, y: [note.Note('E2') for _ in range(2)]}
999    >>> mei.base._processEmbeddedElements(elements, mapping)
1000    [<music21.note.Note D>, <music21.note.Note E>, <music21.note.Note E>, <music21.note.Note D>]
1001    '''
1002    processed = []
1003
1004    for eachElem in elements:
1005        if eachElem.tag in mapping:
1006            result = mapping[eachElem.tag](eachElem, slurBundle)
1007            if isinstance(result, (tuple, list)):
1008                for eachObject in result:
1009                    processed.append(eachObject)
1010            else:
1011                processed.append(result)
1012        elif eachElem.tag not in _IGNORE_UNPROCESSED:
1013            environLocal.printDebug(_UNPROCESSED_SUBELEMENT.format(eachElem.tag, callerTag))
1014
1015    return processed
1016
1017
1018def _timeSigFromAttrs(elem):
1019    '''
1020    From any tag with @meter.count and @meter.unit attributes, make a :class:`TimeSignature`.
1021
1022    :param :class:`~xml.etree.ElementTree.Element` elem: An :class:`Element` with @meter.count and
1023        @meter.unit attributes.
1024    :returns: The corresponding time signature.
1025    :rtype: :class:`~music21.meter.TimeSignature`
1026    '''
1027    return meter.TimeSignature(f"{elem.get('meter.count')!s}/{elem.get('meter.unit')!s}")
1028
1029
1030def _keySigFromAttrs(elem: Element) -> Union[key.Key, key.KeySignature]:
1031    '''
1032    From any tag with (at minimum) either @key.pname or @key.sig attributes, make a
1033    :class:`KeySignature` or :class:`Key`, as possible.
1034
1035    elem is an :class:`Element` with either the @key.pname or @key.sig attribute.
1036
1037    Returns the key or key signature.
1038    '''
1039    if elem.get('key.pname') is not None:
1040        # @key.accid, @key.mode, @key.pname
1041        # noinspection PyTypeChecker
1042        mode = elem.get('key.mode', '')
1043        step = elem.get('key.pname')
1044        accidental = _accidentalFromAttr(elem.get('key.accid'))
1045        if accidental is None:
1046            tonic = step
1047        else:
1048            tonic = step + accidental
1049        return key.Key(tonic=tonic, mode=mode)
1050    else:
1051        # @key.sig, @key.mode
1052        # If @key.mode is null, assume it is a 'major' key (default for ks.asKey)
1053        ks = key.KeySignature(sharps=_sharpsFromAttr(elem.get('key.sig')))
1054        # noinspection PyTypeChecker
1055        return ks.asKey(mode=elem.get('key.mode', 'major'))
1056
1057
1058def _transpositionFromAttrs(elem):
1059    '''
1060    From any element with the @trans.diat and @trans.semi attributes, make an :class:`Interval` that
1061    represents the interval of transposition from written to concert pitch.
1062
1063    :param :class:`~xml.etree.ElementTree.Element` elem: An :class:`Element` with the @trans.diat
1064        and @trans.semi attributes.
1065    :returns: The interval of transposition from written to concert pitch.
1066    :rtype: :class:`music21.interval.Interval`
1067    '''
1068    # noinspection PyTypeChecker
1069    transDiat = int(elem.get('trans.diat', 0))
1070    # noinspection PyTypeChecker
1071    transSemi = int(elem.get('trans.semi', 0))
1072
1073    # If the difference between transSemi and transDiat is greater than five per octave...
1074    # noinspection SpellCheckingInspection
1075    if abs(transSemi - transDiat) > 5 * (abs(transSemi) // 12 + 1):
1076        # ... we need to add octaves to transDiat so it's the proper size. Otherwise,
1077        #     intervalFromGenericAndChromatic() tries to create things like AAAAAAAAA5. Except it
1078        #     actually just fails.
1079        # NB: we test this against transSemi because transDiat could be 0 when transSemi is a
1080        #     multiple of 12 *either* greater or less than 0.
1081        if transSemi < 0:
1082            transDiat -= 7 * (abs(transSemi) // 12)
1083        elif transSemi > 0:
1084            transDiat += 7 * (abs(transSemi) // 12)
1085
1086    # NB: MEI uses zero-based unison rather than 1-based unison, so for music21 we must make every
1087    #     diatonic interval one greater than it was. E.g., '@trans.diat="2"' in MEI means to
1088    #     "transpose up two diatonic steps," which music21 would rephrase as "transpose up by a
1089    #     diatonic third."
1090    if transDiat < 0:
1091        transDiat -= 1
1092    elif transDiat > 0:
1093        transDiat += 1
1094
1095    return interval.intervalFromGenericAndChromatic(interval.GenericInterval(transDiat),
1096                                                    interval.ChromaticInterval(transSemi))
1097
1098
1099# noinspection SpellCheckingInspection
1100def _barlineFromAttr(attr):
1101    '''
1102    Use :func:`_attrTranslator` to convert the value of a "left" or "right" attribute to a
1103    :class:`Barline` or :class:`Repeat` or occasionally a list of :class:`Repeat`. The only time a
1104    list is returned is when "attr" is ``'rptboth'``, in which case the end and start barlines are
1105    both returned.
1106
1107    :param str attr: The MEI @left or @right attribute to convert to a barline.
1108    :returns: The barline.
1109    :rtype: :class:`music21.bar.Barline` or :class:`~music21.bar.Repeat` or list of them
1110    '''
1111    # NB: the MEI Specification says @left is used only for legacy-format conversions, so we'll
1112    #     just assume it's a @right attribute. Not a huge deal if we get this wrong (I hope).
1113    if attr.startswith('rpt'):
1114        if 'rptboth' == attr:
1115            return _barlineFromAttr('rptend'), _barlineFromAttr('rptstart')
1116        elif 'rptend' == attr:
1117            return bar.Repeat('end', times=2)
1118        else:
1119            return bar.Repeat('start')
1120    else:
1121        return bar.Barline(_attrTranslator(attr, 'right', _BAR_ATTR_DICT))
1122
1123
1124def _tieFromAttr(attr):
1125    '''
1126    Convert a @tie attribute to the required :class:`Tie` object.
1127
1128    :param str attr: The MEI @tie attribute to convert.
1129    :return: The relevant :class:`Tie` object.
1130    :rtype: :class:`music21.tie.Tie`
1131    '''
1132    if 'm' in attr or ('t' in attr and 'i' in attr):
1133        return tie.Tie('continue')
1134    elif 'i' in attr:
1135        return tie.Tie('start')
1136    else:
1137        return tie.Tie('stop')
1138
1139
1140def addSlurs(elem, obj, slurBundle):
1141    '''
1142    If relevant, add a slur to an ``obj`` (object) that was created from an ``elem`` (element).
1143
1144    :param elem: The :class:`Element` that caused creation of the ``obj``.
1145    :type elem: :class:`xml.etree.ElementTree.Element`
1146    :param obj: The musical object (:class:`Note`, :class:`Chord`, etc.) created from ``elem``, to
1147        which a slur might be attached.
1148    :type obj: :class:`music21.base.Music21Object`
1149    :param slurBundle: The :class:`Slur`-holding :class:`SpannerBundle` associated with the
1150        :class:`Stream` that holds ``obj``.
1151    :type slurBundle: :class:`music21.spanner.SpannerBundle`
1152    :returns: Whether at least one slur was added.
1153    :rtype: bool
1154
1155    **A Note about Importing Slurs**
1156
1157    Because of how the MEI format specifies slurs, the strategy required for proper import to
1158    music21 is not obvious. There are two ways to specify a slur:
1159
1160    #. With a ``@slur`` attribute, in which case :func:`addSlurs` reads the attribute and manages
1161       creating a :class:`Slur` object, adding the affected objects to it, and storing the
1162       :class:`Slur` in the ``slurBundle``.
1163    #. With a ``<slur>`` element, which requires pre-processing. In this case, :class:`Slur` objects
1164       must already exist in the ``slurBundle``, and special attributes must be added to the
1165       affected elements (``@m21SlurStart`` to the element at the start of the slur and
1166       ``@m21SlurEnd`` to the element at the end). These attributes hold the ``id`` of a
1167       :class:`Slur` in the ``slurBundle``, allowing :func:`addSlurs` to find the slur and add
1168       ``obj`` to it.
1169
1170    .. caution:: If an ``elem`` has an @m21SlurStart or @m21SlurEnd attribute that refer to an
1171        object not found in the ``slurBundle``, the slur is silently dropped.
1172    '''
1173    addedSlur = False
1174
1175    def wrapGetByIdLocal(theId):
1176        "Avoid crashing when getByIdLocl() doesn't find the slur"
1177        try:
1178            slurBundle.getByIdLocal(theId)[0].addSpannedElements(obj)
1179            return True
1180        except IndexError:
1181            # when getByIdLocal() couldn't find the Slur
1182            return False
1183
1184    if elem.get('m21SlurStart') is not None:
1185        addedSlur = wrapGetByIdLocal(elem.get('m21SlurStart'))
1186    if elem.get('m21SlurEnd') is not None:
1187        addedSlur = wrapGetByIdLocal(elem.get('m21SlurEnd'))
1188
1189    if elem.get('slur') is not None:
1190        theseSlurs = elem.get('slur').split(' ')
1191        for eachSlur in theseSlurs:
1192            slurNum, slurType = eachSlur
1193            if 'i' == slurType:
1194                newSlur = spanner.Slur()
1195                newSlur.idLocal = slurNum
1196                slurBundle.append(newSlur)
1197                newSlur.addSpannedElements(obj)
1198                addedSlur = True
1199            elif 't' == slurType:
1200                addedSlur = wrapGetByIdLocal(slurNum)
1201            # 'm' is currently ignored; we may need it for cross-staff slurs
1202
1203    return addedSlur
1204
1205
1206def beamTogether(someThings):
1207    '''
1208    Beam some things together. The function beams every object that has a :attr:`beams` attribute,
1209    leaving the other objects unmodified.
1210
1211    :param someThings: An iterable of things to beam together.
1212    :type someThings: iterable of :class:`~music21.base.Music21Object`
1213    :returns: ``someThings`` with relevant objects beamed together.
1214    :rtype: same as ``someThings``
1215    '''
1216    # Index of the most recent beamedNote/Chord in someThings. Not all Note/Chord objects will
1217    # necessarily be beamed (especially when this is called from tupletFromElement()), so we have
1218    # to make that distinction.
1219    iLastBeamedNote = -1
1220
1221    for i, thing in enumerate(someThings):
1222        if hasattr(thing, 'beams'):
1223            if iLastBeamedNote == -1:
1224                beamType = 'start'
1225            else:
1226                beamType = 'continue'
1227
1228            # checking for len(thing.beams) avoids clobbering beams that were set with a nested
1229            # <beam> element, like a grace note
1230            if duration.convertTypeToNumber(thing.duration.type) > 4 and not thing.beams:
1231                thing.beams.fill(thing.duration.type, beamType)
1232                iLastBeamedNote = i
1233
1234    someThings[iLastBeamedNote].beams.setAll('stop')
1235
1236    return someThings
1237
1238
1239def removeOctothorpe(xmlid):
1240    '''
1241    Given a string with an @xml:id to search for, remove a leading octothorpe, if present.
1242
1243    >>> from music21.mei.base import removeOctothorpe
1244    >>> removeOctothorpe('110a923d-a13a-4a2e-b85c-e1d438e4c5d6')
1245    '110a923d-a13a-4a2e-b85c-e1d438e4c5d6'
1246    >>> removeOctothorpe('#e46cbe82-95fc-4522-9f7a-700e41a40c8e')
1247    'e46cbe82-95fc-4522-9f7a-700e41a40c8e'
1248    '''
1249    if xmlid.startswith('#'):
1250        return xmlid[1:]
1251    else:
1252        return xmlid
1253
1254
1255def makeMetadata(documentRoot):
1256    '''
1257    Produce metadata objects for all the metadata stored in the MEI header.
1258
1259    :param documentRoot: The MEI document's root element.
1260    :type documentRoot: :class:`~xml.etree.ElementTree.Element`
1261    :returns: A :class:`Metadata` object with some of the metadata stored in the MEI document.
1262    :rtype: :class:`music21.metadata.Metadata`
1263    '''
1264    meta = metadata.Metadata()
1265    work = documentRoot.find(f'.//{MEI_NS}work')
1266    if work is not None:
1267        # title, subtitle, and movement name
1268        meta = metaSetTitle(work, meta)
1269        # composer
1270        meta = metaSetComposer(work, meta)
1271        # date
1272        meta = metaSetDate(work, meta)
1273
1274    return meta
1275
1276
1277def metaSetTitle(work, meta):
1278    '''
1279    From a <work> element, find the title, subtitle, and movement name (<tempo> element) and store
1280    the values in a :class:`Metadata` object.
1281
1282    :param work: A <work> :class:`~xml.etree.ElementTree.Element` with metadata you want to find.
1283    :param meta: The :class:`~music21.metadata.Metadata` object in which to store the metadata.
1284    :return: The ``meta`` argument, having relevant metadata added.
1285    '''
1286    # title, subtitle, and movement name
1287    for title in work.findall(f'./{MEI_NS}titleStmt/{MEI_NS}title'):
1288        if title.get('type', '') == 'subtitle':
1289            meta.subtitle = title.text
1290        elif meta.title is None:
1291            meta.title = title.text
1292
1293    if hasattr(meta, 'subtitle'):
1294        # Since m21.Metadata doesn't actually have a "subtitle" attribute, we'll put the subtitle
1295        # in the title
1296        meta.title = f'{meta.title} ({meta.subtitle})'
1297        del meta.subtitle
1298
1299    tempo = work.find(f'./{MEI_NS}tempo')
1300    if tempo is not None:
1301        meta.movementName = tempo.text
1302
1303    return meta
1304
1305
1306def metaSetComposer(work, meta):
1307    '''
1308    From a <work> element, find the composer(s) and store the values in a :class:`Metadata` object.
1309
1310    :param work: A <work> :class:`~xml.etree.ElementTree.Element` with metadata you want to find.
1311    :param meta: The :class:`~music21.metadata.Metadata` object in which to store the metadata.
1312    :return: The ``meta`` argument, having relevant metadata added.
1313    '''
1314    composers = []
1315    for persName in work.findall(f'./{MEI_NS}titleStmt/{MEI_NS}respStmt/{MEI_NS}persName'):
1316        if persName.get('role') == 'composer' and persName.text:
1317            composers.append(persName.text)
1318    for composer in work.findall(f'./{MEI_NS}titleStmt/{MEI_NS}composer'):
1319        if composer.text:
1320            composers.append(composer.text)
1321        else:
1322            persName = composer.find(f'./{MEI_NS}persName')
1323            if persName.text:
1324                composers.append(persName.text)
1325    if len(composers) == 1:
1326        meta.composer = composers[0]
1327    elif len(composers) > 1:
1328        meta.composer = composers
1329
1330    return meta
1331
1332
1333def metaSetDate(work, meta):
1334    '''
1335    From a <work> element, find the date (range) of composition and store the values in a
1336    :class:`Metadata` object.
1337
1338    :param work: A <work> :class:`~xml.etree.ElementTree.Element` with metadata you want to find.
1339    :param meta: The :class:`~music21.metadata.Metadata` object in which to store the metadata.
1340    :return: The ``meta`` argument, having relevant metadata added.
1341    '''
1342    date = work.find(f'./{MEI_NS}history/{MEI_NS}creation/{MEI_NS}date')
1343    if date is not None:  # must use explicit "is not None" for an Element
1344        if date.text or date.get('isodate'):
1345            dateStr = date.get('isodate') if date.get('isodate') else date.text
1346            theDate = metadata.Date()
1347            try:
1348                theDate.loadStr(dateStr.replace('-', '/'))
1349            except ValueError:
1350                environLocal.warn(_MISSED_DATE.format(dateStr))
1351            else:
1352                meta.date = theDate
1353        else:
1354            dateStart = date.get('notbefore') if date.get('notbefore') else date.get('startdate')
1355            dateEnd = date.get('notafter') if date.get('notafter') else date.get('enddate')
1356            if dateStart and dateEnd:
1357                meta.date = metadata.DateBetween((dateStart, dateEnd))
1358
1359    return meta
1360
1361
1362def getVoiceId(fromThese):
1363    '''
1364    From a list of objects with mixed type, find the "id" of the :class:`music21.stream.Voice`
1365    instance.
1366
1367    :param list fromThese: A list of objects of any type, at least one of which must be a
1368        :class:`~music21.stream.Voice` instance.
1369    :returns: The ``id`` of the :class:`Voice` instance.
1370    :raises: :exc:`RuntimeError` if zero or many :class:`Voice` objects are found.
1371    '''
1372    fromThese = [item for item in fromThese if isinstance(item, stream.Voice)]
1373    if len(fromThese) == 1:
1374        return fromThese[0].id
1375    else:
1376        raise RuntimeError('getVoiceId: found too few or too many Voice objects')
1377
1378# noinspection PyTypeChecker
1379def scaleToTuplet(objs, elem):
1380    '''
1381    Scale the duration of some objects by a ratio indicated by a tuplet. The ``elem`` must have the
1382    @m21TupletNum and @m21TupletNumbase attributes set, and optionally the @m21TupletSearch or
1383    @m21TupletType attributes.
1384
1385    The @m21TupletNum and @m21TupletNumbase attributes should be equal to the @num and @numbase
1386    values of the <tuplet> or <tupletSpan> that indicates this tuplet.
1387
1388    The @m21TupletSearch attribute, whose value must either be ``'start'`` or ``'end'``, is required
1389    when a <tupletSpan> does not include a @plist attribute. It indicates that the importer must
1390    "search" for a tuplet near the end of the import process, which involves scaling the durations
1391    of all objects discovered between those with the "start" and "end" search values.
1392
1393    The @m21TupletType attribute is set directly as the :attr:`type` attribute of the music21
1394    object's :class:`Tuplet` object. If @m21TupletType is not set, the @tuplet attribute will be
1395    consulted. Note that this attribute is ignored if the @m21TupletSearch attribute is present,
1396    since the ``type`` will be set later by the tuplet-finding algorithm.
1397
1398    .. note:: Objects without a :attr:`duration` attribute will be skipped silently, unless they
1399        will be given the @m21TupletSearch attribute.
1400
1401    :param objs: The object(s) whose durations will be scaled.
1402        You may provide either a single object
1403        or an iterable; the return type corresponds to the input type.
1404    :type objs: (list of) :class:`~music21.base.Music21Object`
1405    :param elem: An :class:`Element` with the appropriate attributes (as specified above).
1406    :type elem: :class:`xml.etree.ElementTree.Element`
1407    :returns: ``objs`` with scaled durations.
1408    :rtype: (list of) :class:`~music21.base.Music21Object`
1409    '''
1410    if not isinstance(objs, (list, set, tuple)):
1411        objs = [objs]
1412        wasList = False
1413    else:
1414        wasList = True
1415
1416    for obj in objs:
1417        if not isinstance(obj, (note.Note, note.Rest, chord.Chord)):
1418            # silently skip objects that don't have a duration
1419            continue
1420
1421        elif elem.get('m21TupletSearch') is not None:
1422            obj.m21TupletSearch = elem.get('m21TupletSearch')
1423            obj.m21TupletNum = elem.get('m21TupletNum')
1424            obj.m21TupletNumbase = elem.get('m21TupletNumbase')
1425
1426        else:
1427            obj.duration.appendTuplet(duration.Tuplet(
1428                numberNotesActual=int(elem.get('m21TupletNum')),
1429                numberNotesNormal=int(elem.get('m21TupletNumbase')),
1430                durationNormal=obj.duration.type,
1431                durationActual=obj.duration.type))
1432
1433            if elem.get('m21TupletType') is not None:
1434                obj.duration.tuplets[0].type = elem.get('m21TupletType')
1435            elif elem.get('tuplet', '').startswith('i'):
1436                obj.duration.tuplets[0].type = 'start'
1437            elif elem.get('tuplet', '').startswith('t'):
1438                obj.duration.tuplets[0].type = 'stop'
1439
1440    if wasList:
1441        return objs
1442    else:
1443        return objs[0]
1444
1445
1446def _guessTuplets(theLayer):
1447    # TODO: nested tuplets don't work when they're both specified with <tupletSpan>
1448    # TODO: adjust this to work with cross-measure tuplets (i.e., where only the "start" or "end"
1449    #       is found in theLayer)
1450    '''
1451    Given a list of music21 objects, possibly containing :attr:`m21TupletSearch`,
1452    :attr:`m21TupletNum`, and :attr:`m21TupletNumbase` attributes, adjust the durations of the
1453    objects as specified by those "m21Tuplet" attributes, then remove the attributes.
1454
1455    This function finishes processing for tuplets encoded as a <tupletSpan> where @startid and
1456    @endid are indicated, but not @plist. Knowing the starting and ending object in the tuplet, we
1457    can guess that all the Note, Rest, and Chord objects between the starting and ending objects
1458    in that <layer> are part of the tuplet. (Grace notes retain a 0.0 duration).
1459
1460    .. note:: At the moment, this will likely only work for simple tuplets---not nested tuplets.
1461
1462    :param theLayer: Objects from the <layer> in which to search for objects that have the
1463        :attr:`m21TupletSearch` attribute.
1464    :type theScore: list
1465    :returns: The same list, with durations adjusted to account for tuplets.
1466    '''
1467    # NB: this is a hidden function because it uses the "m21TupletSearch" attribute, which are only
1468    #     supposed to be used within the MEI import module
1469
1470    inATuplet = False  # we hit m21TupletSearch=='start' but not 'end' yet
1471    tupletNum = None
1472    tupletNumbase = None
1473
1474    for eachNote in theLayer:
1475        # we'll skip objects that don't have a duration
1476        if not isinstance(eachNote, (note.Note, note.Rest, chord.Chord)):
1477            continue
1478
1479        if hasattr(eachNote, 'm21TupletSearch') and eachNote.m21TupletSearch == 'start':
1480            inATuplet = True
1481            tupletNum = int(eachNote.m21TupletNum)
1482            tupletNumbase = int(eachNote.m21TupletNumbase)
1483
1484            del eachNote.m21TupletSearch
1485            del eachNote.m21TupletNum
1486            del eachNote.m21TupletNumbase
1487
1488        if inATuplet:
1489            scaleToTuplet(eachNote, Element('',
1490                                            m21TupletNum=str(tupletNum),
1491                                            m21TupletNumbase=str(tupletNumbase)))
1492
1493            if hasattr(eachNote, 'm21TupletSearch') and eachNote.m21TupletSearch == 'end':
1494                # we've reached the end of the tuplet!
1495                eachNote.duration.tuplets[0].type = 'stop'
1496
1497                del eachNote.m21TupletSearch
1498                del eachNote.m21TupletNum
1499                del eachNote.m21TupletNumbase
1500
1501                # reset the tuplet-tracking variables
1502                inATuplet = False
1503
1504    return theLayer
1505
1506
1507# Element-Based Converter Functions
1508# -----------------------------------------------------------------------------
1509def scoreDefFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
1510    '''
1511    <scoreDef> Container for score meta-information.
1512
1513    In MEI 2013: pg.431 (445 in PDF) (MEI.shared module)
1514
1515    This function returns a dictionary with objects that may relate to the entire score, to all
1516    parts at a particular moment, or only to a specific part at a particular moment. The dictionary
1517    keys determine the object's scope. If the key is...
1518
1519    * ``'whole-score objects'``, it applies to the entire score (e.g., page size);
1520    * ``'all-part objects'``, it applies to all parts at the moment this <scoreDef> appears;
1521    * the @n attribute of a part, it applies only to
1522      that part at the moment this <scoreDef> appears.
1523
1524    While the multi-part objects will be held in a list, the single-part objects will be in a dict
1525    like that returned by :func:`staffDefFromElement`.
1526
1527    Note that it is the caller's responsibility to determine the right action if there are
1528    conflicting objects in the returned dictionary.
1529
1530    For example:
1531
1532    >>> meiDoc = """<?xml version="1.0" encoding="UTF-8"?>
1533    ... <scoreDef meter.count="3" meter.unit="4" xmlns="http://www.music-encoding.org/ns/mei">
1534    ...     <staffGrp>
1535    ...         <staffDef n="1" label="Clarinet"/>
1536    ...         <staffGrp>
1537    ...             <staffDef n="2" label="Flute"/>
1538    ...             <staffDef n="3" label="Violin"/>
1539    ...         </staffGrp>
1540    ...     </staffGrp>
1541    ... </scoreDef>
1542    ... """
1543    >>> from music21 import *
1544    >>> from xml.etree import ElementTree as ET
1545    >>> scoreDef = ET.fromstring(meiDoc)
1546    >>> result = mei.base.scoreDefFromElement(scoreDef)
1547    >>> len(result)
1548    5
1549    >>> result['1']
1550    {'instrument': <music21.instrument.Clarinet '1: Clarinet: Clarinet'>}
1551    >>> result['3']
1552    {'instrument': <music21.instrument.Violin '3: Violin: Violin'>}
1553    >>> result['all-part objects']
1554    [<music21.meter.TimeSignature 3/4>]
1555    >>> result['whole-score objects']
1556    []
1557
1558    :param elem: The ``<scoreDef>`` element to process.
1559    :type elem: :class:`~xml.etree.ElementTree.Element`
1560    :returns: Objects from the ``<scoreDef>``, as described above.
1561    :rtype: dict
1562
1563    **Attributes/Elements Implemented:**
1564
1565    - (att.meterSigDefault.log (@meter.count, @meter.unit))
1566    - (att.keySigDefault.log (@key.accid, @key.mode, @key.pname, @key.sig))
1567    - contained <staffGrp>
1568
1569    **Attributes/Elements in Testing:** None
1570
1571    **Attributes not Implemented:**
1572
1573    - att.common (@label, @n, @xml:base) (att.id (@xml:id))
1574    - att.scoreDef.log
1575
1576        - (att.cleffing.log (@clef.shape, @clef.line, @clef.dis, @clef.dis.place))
1577        - (att.duration.default (@dur.default, @num.default, @numbase.default))
1578        - (att.keySigDefault.log (@key.sig.mixed))
1579        - (att.octavedefault (@octave.default))
1580        - (att.transposition (@trans.diat, @trans.semi))
1581        - (att.scoreDef.log.cmn (att.beaming.log (@beam.group, @beam.rests)))
1582        - (att.scoreDef.log.mensural
1583
1584            - (att.mensural.log (@mensur.dot, @mensur.sign,
1585                 @mensur.slash, @proport.num, @proport.numbase)
1586            - (att.mensural.shared (@modusmaior, @modusminor, @prolatio, @tempus))))
1587
1588    - att.scoreDef.vis (all)
1589    - att.scoreDef.ges (all)
1590    - att.scoreDef.anl (none exist)
1591
1592    **Contained Elements not Implemented:**
1593
1594    - MEI.cmn: meterSig meterSigGrp
1595    - MEI.harmony: chordTable
1596    - MEI.linkalign: timeline
1597    - MEI.midi: instrGrp
1598    - MEI.shared: keySig pgFoot pgFoot2 pgHead pgHead2
1599    - MEI.usersymbols: symbolTable
1600    '''
1601
1602    # make the dict
1603    allParts = 'all-part objects'
1604    wholeScore = 'whole-score objects'
1605    post = {allParts: [], wholeScore: []}
1606
1607    # 1.) process all-part attributes
1608    # --> time signature
1609    if elem.get('meter.count') is not None:
1610        post[allParts].append(_timeSigFromAttrs(elem))
1611
1612    # --> key signature
1613    if elem.get('key.pname') is not None or elem.get('key.sig') is not None:
1614        post[allParts].append(_keySigFromAttrs(elem))
1615
1616    # 2.) staff-specific things (from contained <staffGrp> >> <staffDef>)
1617    for eachGrp in elem.iterfind(f'{MEI_NS}staffGrp'):
1618        post.update(staffGrpFromElement(eachGrp, slurBundle))
1619
1620    return post
1621
1622
1623def staffGrpFromElement(elem, slurBundle=None, staffDefDict=None):
1624    '''
1625    <staffGrp> A group of bracketed or braced staves.
1626
1627    In MEI 2013: pg.448 (462 in PDF) (MEI.shared module)
1628
1629    For now, this function is merely a container-processor  for <staffDef> elements contained
1630    in this <staffGrp> element given as the "elem" argument. That is, the function does not yet
1631    create the brackets/braces and labels expected of a staff group.
1632    Note however that all <staffDef>
1633    elements will be processed, even if they're contained within several layers of <staffGrp>.
1634
1635    :param elem: The ``<staffGrp>`` element to process.
1636    :type elem: :class:`~xml.etree.ElementTree.Element`
1637    :returns: Dictionary where keys are the @n attribute on a contained <staffDef>, and values are
1638        the result of calling :func:`staffDefFromElement` with that <staffDef>.
1639
1640    **Attributes/Elements Implemented:**
1641
1642    - contained <staffDef>
1643    - contained <staffGrp>
1644
1645    **Attributes/Elements in Testing:** none
1646
1647    **Attributes not Implemented:**
1648
1649    - att.common (@label, @n, @xml:base) (att.id (@xml:id))
1650    - att.declaring (@decls)
1651    - att.facsimile (@facs)
1652    - att.staffGrp.vis (@barthru)
1653
1654        - (att.labels.addl (@label.abbr))
1655        - (att.staffgroupingsym (@symbol))
1656        - (att.visibility (@visible))
1657
1658    - att.staffGrp.ges (att.instrumentident (@instr))
1659
1660    **Contained Elements not Implemented:**
1661
1662    - MEI.midi: instrDef
1663    - MEI.shared: grpSym label
1664    '''
1665
1666    staffDefTag = f'{MEI_NS}staffDef'
1667    staffGroupTag = f'{MEI_NS}staffGrp'
1668
1669    staffDefDict = staffDefDict if staffDefDict is not None else {}
1670
1671    for el in elem.findall('*'):
1672        # return all staff defs in this staff group
1673        if el.tag == staffDefTag:
1674            staffDefDict[el.get('n')] = staffDefFromElement(el, slurBundle)
1675
1676        # recurse if there are more groups, append to the working staffDefDict
1677        elif el.tag == staffGroupTag:
1678            staffGrpFromElement(el, slurBundle, staffDefDict)
1679
1680    return staffDefDict
1681
1682
1683def staffDefFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
1684    '''
1685    <staffDef> Container for staff meta-information.
1686
1687    In MEI 2013: pg.445 (459 in PDF) (MEI.shared module)
1688
1689    :returns: A dict with various types of metadata information, depending on what is specified in
1690        this <staffDef> element. Read below for more information.
1691    :rtype: dict
1692
1693    **Possible Return Values**
1694
1695    The contents of the returned dictionary depend on the contents of the <staffDef> element. The
1696    dictionary keys correspond to types of information. Possible keys include:
1697
1698    - ``'instrument'``: for a :class:`music21.instrument.Instrument` subclass
1699    - ``'clef'``: for a :class:`music21.clef.Clef` subclass
1700    - ``'key'``: for a :class:`music21.key.Key` or :class:`~music21.key.KeySignature` subclass
1701    - ``'meter'``: for a :class:`music21.meter.TimeSignature`
1702
1703    **Examples**
1704
1705    This <staffDef> only returns a single item.
1706
1707    >>> meiDoc = """<?xml version="1.0" encoding="UTF-8"?>
1708    ... <staffDef n="1" label="Clarinet" xmlns="http://www.music-encoding.org/ns/mei"/>
1709    ... """
1710    >>> from music21 import *
1711    >>> from xml.etree import ElementTree as ET
1712    >>> staffDef = ET.fromstring(meiDoc)
1713    >>> result = mei.base.staffDefFromElement(staffDef)
1714    >>> len(result)
1715    1
1716    >>> result
1717    {'instrument': <music21.instrument.Clarinet '1: Clarinet: Clarinet'>}
1718    >>> result['instrument'].partId
1719    '1'
1720    >>> result['instrument'].partName
1721    'Clarinet'
1722
1723    This <staffDef> returns many objects.
1724
1725    >>> meiDoc = """<?xml version="1.0" encoding="UTF-8"?>
1726    ... <staffDef n="2" label="Tuba" key.pname="B" key.accid="f" key.mode="major"
1727    ...  xmlns="http://www.music-encoding.org/ns/mei">
1728    ...     <clef shape="F" line="4"/>
1729    ... </staffDef>
1730    ... """
1731    >>> from music21 import *
1732    >>> from xml.etree import ElementTree as ET
1733    >>> staffDef = ET.fromstring(meiDoc)
1734    >>> result = mei.base.staffDefFromElement(staffDef)
1735    >>> len(result)
1736    3
1737    >>> result['instrument']
1738    <music21.instrument.Tuba '2: Tuba: Tuba'>
1739    >>> result['clef']
1740    <music21.clef.BassClef>
1741    >>> result['key']
1742    <music21.key.Key of B- major>
1743
1744    **Attributes/Elements Implemented:**
1745
1746    - @label (att.common) as Instrument.partName
1747    - @label.abbr (att.labels.addl) as Instrument.partAbbreviation
1748    - @n (att.common) as Instrument.partId
1749    - (att.keySigDefault.log (@key.accid, @key.mode, @key.pname, @key.sig))
1750    - (att.meterSigDefault.log (@meter.count, @meter.unit))
1751    - (att.cleffing.log (@clef.shape, @clef.line, @clef.dis, @clef.dis.place))
1752      (via :func:`clefFromElement`)
1753    - @trans.diat and @trans.demi (att.transposition)
1754    - <instrDef> held within
1755    - <clef> held within
1756
1757    **Attributes/Elements Ignored:**
1758
1759    - @key.sig.mixed (from att.keySigDefault.log)
1760
1761    **Attributes/Elements in Testing:** none
1762
1763    **Attributes not Implemented:**
1764
1765    - att.common (@n, @xml:base) (att.id (@xml:id))
1766    - att.declaring (@decls)
1767    - att.staffDef.log
1768
1769        - (att.duration.default (@dur.default, @num.default, @numbase.default))
1770        - (att.octavedefault (@octave.default))
1771        - (att.staffDef.log.cmn (att.beaming.log (@beam.group, @beam.rests)))
1772        - (att.staffDef.log.mensural
1773
1774            - (att.mensural.log (@mensur.dot, @mensur.sign, @mensur.slash,
1775                                 @proport.num, @proport.numbase)
1776            - (att.mensural.shared (@modusmaior, @modusminor, @prolatio, @tempus))))
1777
1778    - att.staffDef.vis (all)
1779    - att.staffDef.ges (all)
1780    - att.staffDef.anl (none exist)
1781
1782    **Contained Elements not Implemented:**
1783
1784    - MEI.cmn: meterSig meterSigGrp
1785    - MEI.mensural: mensural support
1786    - MEI.shared: clefGrp keySig label layerDef
1787    '''
1788    # mapping from tag name to our converter function
1789    tagToFunction = {f'{MEI_NS}clef': clefFromElement}
1790
1791    # first make the Instrument
1792    post = elem.find(f'{MEI_NS}instrDef')
1793    if post is not None:
1794        post = {'instrument': instrDefFromElement(post)}
1795    else:
1796        try:
1797            post = {'instrument': instrument.fromString(elem.get('label', ''))}
1798        except instrument.InstrumentException:
1799            post = {}
1800
1801    if 'instrument' in post:
1802        post['instrument'].partName = elem.get('label')
1803        post['instrument'].partAbbreviation = elem.get('label.abbr')
1804        post['instrument'].partId = elem.get('n')
1805
1806    # --> transposition
1807    if elem.get('trans.semi') is not None:
1808        if 'instrument' not in post:
1809            post['instrument'] = instrument.Instrument()
1810        post['instrument'].transposition = _transpositionFromAttrs(elem)
1811
1812    # process other part-specific information
1813    # --> time signature
1814    if elem.get('meter.count') is not None:
1815        post['meter'] = _timeSigFromAttrs(elem)
1816
1817    # --> key signature
1818    if elem.get('key.pname') is not None or elem.get('key.sig') is not None:
1819        post['key'] = _keySigFromAttrs(elem)
1820
1821    # --> clef
1822    if elem.get('clef.shape') is not None:
1823        el = Element(
1824            'clef', {
1825                'shape': elem.get('clef.shape'),
1826                'line': elem.get('clef.line'),
1827                'dis': elem.get('clef.dis'),
1828                'dis.place': elem.get('clef.dis.place')
1829            }
1830        )
1831        post['clef'] = clefFromElement(el)
1832
1833    embeddedItems = _processEmbeddedElements(elem.findall('*'), tagToFunction, elem.tag, slurBundle)
1834    for eachItem in embeddedItems:
1835        if isinstance(eachItem, clef.Clef):
1836            post['clef'] = eachItem
1837
1838    return post
1839
1840
1841def dotFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
1842    '''
1843    Returns ``1`` no matter what is passed in.
1844
1845    <dot> Dot of augmentation or division.
1846
1847    In MEI 2013: pg.304 (318 in PDF) (MEI.shared module)
1848
1849    :returns: 1
1850    :rtype: int
1851
1852    **Attributes/Elements Implemented:** none
1853
1854    **Attributes/Elements in Testing:** none
1855
1856    **Attributes not Implemented:**
1857
1858    - att.common (@label, @n, @xml:base) (att.id (@xml:id))
1859    - att.facsimile (@facs)
1860    - att.dot.log (all)
1861    - att.dot.vis (all)
1862    - att.dot.gesatt.dot.anl (all)
1863
1864    **Elements not Implemented:** none
1865    '''
1866    return 1
1867
1868
1869def articFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
1870    '''
1871    <artic> An indication of how to play a note or chord.
1872
1873    In MEI 2013: pg.259 (273 in PDF) (MEI.shared module)
1874
1875    :returns: A list of :class:`~music21.articulations.Articulation` objects.
1876
1877    **Examples**
1878
1879    This function is normally called by, for example, :func:`noteFromElement`, to determine the
1880    :class:`Articulation` objects that will be assigned to the
1881    :attr:`~music21.note.GeneralNote.articulations` attribute.
1882
1883    >>> from xml.etree import ElementTree as ET
1884    >>> from music21 import *
1885    >>> meiSnippet = """<artic artic="acc" xmlns="http://www.music-encoding.org/ns/mei"/>"""
1886    >>> meiSnippet = ET.fromstring(meiSnippet)
1887    >>> mei.base.articFromElement(meiSnippet)
1888    [<music21.articulations.Accent>]
1889
1890    A single <artic> element may indicate many :class:`Articulation` objects.
1891
1892    >>> meiSnippet = """<artic artic="acc ten" xmlns="http://www.music-encoding.org/ns/mei"/>"""
1893    >>> meiSnippet = ET.fromstring(meiSnippet)
1894    >>> mei.base.articFromElement(meiSnippet)
1895    [<music21.articulations.Accent>, <music21.articulations.Tenuto>]
1896
1897    **Attributes Implemented:**
1898
1899    - @artic
1900
1901    **Attributes/Elements in Testing:** none
1902
1903    **Attributes not Implemented:**
1904
1905    - att.common (@label, @n, @xml:base) (att.id (@xml:id))
1906    - att.facsimile (@facs)
1907    - att.typography (@fontfam, @fontname, @fontsize, @fontstyle, @fontweight)
1908    - att.artic.log
1909
1910        - (att.controlevent
1911
1912            - (att.plist (@plist, @evaluate))
1913            - (att.timestamp.musical (@tstamp))
1914            - (att.timestamp.performed (@tstamp.ges, @tstamp.real))
1915            - (att.staffident (@staff))
1916            - (att.layerident (@layer)))
1917
1918    - att.artic.vis (all)
1919    - att.artic.gesatt.artic.anl (all)
1920
1921    **Contained Elements not Implemented:** none
1922    '''
1923    articElement = elem.get('artic')
1924    if articElement is not None:
1925        return _makeArticList(articElement)
1926    else:
1927        return []
1928
1929
1930def accidFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
1931    '''
1932    <accid> Records a temporary alteration to the pitch of a note.
1933
1934    In MEI 2013: pg.248 (262 in PDF) (MEI.shared module)
1935
1936    :returns: A string indicating the music21 representation of this accidental.
1937
1938    **Examples**
1939
1940    Unlike most of the ___FromElement() functions, this does not return any music21 object---just
1941    a string. Accidentals up to triple-sharp and triple-flat are supported.
1942
1943    >>> from xml.etree import ElementTree as ET
1944    >>> from music21 import *
1945    >>> meiSnippet = """<accid accid="s" xmlns="http://www.music-encoding.org/ns/mei"/>"""
1946    >>> meiSnippet = ET.fromstring(meiSnippet)
1947    >>> mei.base.accidFromElement(meiSnippet)
1948    '#'
1949    >>> meiSnippet = """<accid accid="tf" xmlns="http://www.music-encoding.org/ns/mei"/>"""
1950    >>> meiSnippet = ET.fromstring(meiSnippet)
1951    >>> mei.base.accidFromElement(meiSnippet)
1952    '---'
1953
1954    **Attributes/Elements Implemented:**
1955
1956    - @accid (from att.accid.log)
1957    - @accid.ges (from att.accid.ges)
1958
1959    .. note:: If set, the @accid.ges attribute is always imported as the music21 :class:`Accidental`
1960        for this note. We assume it corresponds to the accidental implied by a key signature.
1961
1962    **Attributes/Elements in Testing:** none
1963
1964    **Attributes not Implemented:**
1965
1966    - att.common (@label, @n, @xml:base) (att.id (@xml:id))
1967    - att.facsimile (@facs)
1968    - att.typography (@fontfam, @fontname, @fontsize, @fontstyle, @fontweight)
1969    - att.accid.log (@func)
1970
1971        - (att.controlevent
1972
1973            - (att.plist (@plist, @evaluate))
1974            - (att.timestamp.musical (@tstamp))
1975            - (att.timestamp.performed (@tstamp.ges, @tstamp.real))
1976            - (att.staffident (@staff)) (att.layerident (@layer)))
1977
1978    - att.accid.vis (all)
1979    - att.accid.anl (all)
1980
1981    **Contained Elements not Implemented:** none
1982    '''
1983    if elem.get('accid.ges') is not None:
1984        return _accidGesFromAttr(elem.get('accid.ges', ''))
1985    else:
1986        return _accidentalFromAttr(elem.get('accid'))
1987
1988
1989def sylFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
1990    '''
1991    <syl> Individual lyric syllable.
1992
1993    In MEI 2013: pg.454 (468 in PDF) (MEI.shared module)
1994
1995    :returns: An appropriately-configured :class:`music21.note.Lyric`.
1996
1997    **Attributes/Elements Implemented:**
1998
1999    - @con and @wordpos (from att.syl.log)
2000
2001    **Attributes/Elements in Testing:** none
2002
2003    **Attributes not Implemented:**
2004
2005    - att.common (@label, @n, @xml:base) (att.id (@xml:id))
2006    - att.facsimile (@facs)
2007    - att.syl.vis (att.typography (@fontfam, @fontname, @fontsize, @fontstyle, @fontweight))
2008
2009        - (att.visualoffset (att.visualoffset.ho (@ho))
2010
2011            - (att.visualoffset.to (@to))
2012            - (att.visualoffset.vo (@vo)))
2013
2014        - (att.xy (@x, @y))
2015        - (att.horizontalalign (@halign))
2016
2017    - att.syl.anl (att.common.anl (@copyof, @corresp, @next, @prev, @sameas, @synch)
2018
2019        -  (att.alignment (@when)))
2020
2021    **Contained Elements not Implemented:**
2022
2023    - MEI.edittrans: (all)
2024    - MEI.figtable: fig
2025    - MEI.namesdates: corpName geogName periodName persName styleName
2026    - MEI.ptrref: ptr ref
2027    - MEI.shared: address bibl date identifier lb name num rend repository stack title
2028    '''
2029    wordPos = elem.get('wordpos')
2030    wordPosDict = {'i': 'begin', 'm': 'middle', 't': 'end', None: None}
2031
2032    conDict = {'s': ' ', 'd': '-', 't': '~', 'u': '_', None: '-'}
2033    if 'i' == wordPos:
2034        text = elem.text + conDict[elem.get('con')]
2035    elif 'm' == wordPos:
2036        text = conDict[elem.get('con')] + elem.text + conDict[elem.get('con')]
2037    elif 't' == wordPos:
2038        text = conDict[elem.get('con')] + elem.text
2039    else:
2040        text = elem.text
2041
2042    syllabic = wordPosDict[wordPos]
2043
2044    if syllabic:
2045        return note.Lyric(text=text, syllabic=syllabic, applyRaw=True)
2046    else:
2047        return note.Lyric(text=text)
2048
2049
2050def verseFromElement(elem, backupN=None, slurBundle=None):  # pylint: disable=unused-argument
2051    '''
2052    <verse> Lyric verse.
2053
2054    In MEI 2013: pg.480 (494 in PDF) (MEI.lyrics module)
2055
2056    :param int backupN: The backup verse number to use if no @n attribute exists on ``elem``.
2057    :returns: The appropriately-configured :class:`Lyric` objects.
2058    :rtype: list of :class:`music21.note.Lyric`
2059
2060    **Attributes/Elements Implemented:**
2061
2062    - @n and <syl>
2063
2064    **Attributes/Elements in Testing:** none
2065
2066    **Attributes not Implemented:**
2067
2068    - att.common (@label, @n, @xml:base) (att.id (@xml:id))
2069    - att.facsimile (@facs)
2070    - att.lang (@xml:lang)
2071    - att.verse.log (@refrain, @rhythm)
2072    - att.verse.vis (att.typography (@fontfam, @fontname, @fontsize, @fontstyle, @fontweight))
2073
2074        - (att.visualoffset.to (@to))
2075        - ((att.visualoffset.vo (@vo))
2076
2077            - (att.xy (@x, @y))
2078
2079    - att.verse.anl (att.common.anl (@copyof, @corresp, @next, @prev, @sameas, @synch)
2080
2081        - (att.alignment (@when)))
2082
2083    **Contained Elements not Implemented:**
2084
2085    - MEI.shared: dir dynam lb space tempo
2086    '''
2087    syllables = [sylFromElement(s) for s in elem.findall(f'./{MEI_NS}syl')]
2088    for eachSyl in syllables:
2089        try:
2090            eachSyl.number = int(elem.get('n', backupN))
2091        except (TypeError, ValueError):
2092            environLocal.warn(_BAD_VERSE_NUMBER.format(elem.get('n', backupN)))
2093    return syllables
2094
2095
2096def noteFromElement(elem, slurBundle=None):
2097    # NOTE: this function should stay in sync with chordFromElement() where sensible
2098    '''
2099    <note> is a single pitched event.
2100
2101    In MEI 2013: pg.382 (396 in PDF) (MEI.shared module)
2102
2103    .. note:: If set, the @accid.ges attribute is always imported as the music21 :class:`Accidental`
2104        for this note. We assume it corresponds to the accidental implied by a key signature.
2105
2106    .. note:: If ``elem`` contains both <syl> and <verse> elements as immediate children, the lyrics
2107        indicated with <verse> element(s) will always obliterate those given indicated with <syl>
2108        elements.
2109
2110    **Attributes/Elements Implemented:**
2111
2112    - @accid and <accid>
2113    - @accid.ges for key signatures
2114    - @pname, from att.pitch: [a--g]
2115    - @oct, from att.octave: [0..9]
2116    - @dur, from att.duration.musical: (via _qlDurationFromAttr())
2117    - @dots: [0..4], and <dot> contained within
2118    - @xml:id (or id), an XML id (submitted as the Music21Object "id")
2119    - @artic and <artic>
2120    - @tie, (many of "[i|m|t]")
2121    - @slur, (many of "[i|m|t][1-6]")
2122    - @grace, from att.note.ges.cmn: partial implementation (notes marked as grace, but the
2123        duration is 0 because we ignore the question of which neighbouring note to borrow time from)
2124    - <syl> and <verse>
2125
2126    **Attributes/Elements in Testing:** none
2127
2128    **Attributes not Implemented:**
2129
2130    - att.common (@label, @n, @xml:base)
2131    - att.facsimile (@facs)
2132    - att.note.log
2133
2134        - (att.event
2135
2136            - (att.timestamp.musical (@tstamp))
2137            - (att.timestamp.performed (@tstamp.ges, @tstamp.real))
2138            - (att.staffident (@staff))
2139            - (att.layerident (@layer)))
2140
2141        - (att.fermatapresent (@fermata))
2142        - (att.syltext (@syl))
2143        - (att.note.log.cmn
2144
2145            - (att.tupletpresent (@tuplet))
2146            - (att.beamed (@beam))
2147            - (att.lvpresent (@lv))
2148            - (att.ornam (@ornam)))
2149
2150        - (att.note.log.mensural (@lig))
2151
2152    - att.note.vis (all)
2153
2154    - att.note.ges
2155
2156        - (@oct.ges, @pname.ges, @pnum)
2157        - att.articulation.performed (@artic.ges))
2158        - (att.duration.performed (@dur.ges))
2159        - (att.instrumentident (@instr))
2160        - (att.note.ges.cmn (@gliss)
2161
2162            - (att.graced (@grace, @grace.time)))  <-- partially implemented
2163
2164        - (att.note.ges.mensural (att.duration.ratio (@num, @numbase)))
2165        - (att.note.ges.tablature (@tab.fret, @tab.string))
2166
2167    - att.note.anl (all)
2168
2169    **Contained Elements not Implemented:**
2170
2171    - MEI.critapp: app
2172    - MEI.edittrans: (all)
2173    '''
2174    tagToFunction = {f'{MEI_NS}dot': dotFromElement,
2175                     f'{MEI_NS}artic': articFromElement,
2176                     f'{MEI_NS}accid': accidFromElement,
2177                     f'{MEI_NS}syl': sylFromElement}
2178
2179    # start with a Note with Pitch
2180    theNote = _accidentalFromAttr(elem.get('accid'))
2181    theNote = safePitch(elem.get('pname', ''), theNote, elem.get('oct', ''))
2182    theNote = note.Note(theNote)
2183
2184    # set the Note's duration
2185    theDuration = _qlDurationFromAttr(elem.get('dur'))
2186    theDuration = makeDuration(theDuration, int(elem.get('dots', 0)))
2187    theNote.duration = theDuration
2188
2189    # iterate all immediate children
2190    dotElements = 0  # count the number of <dot> elements
2191    for subElement in _processEmbeddedElements(elem.findall('*'),
2192                                               tagToFunction,
2193                                               elem.tag,
2194                                               slurBundle):
2195        if isinstance(subElement, int):
2196            dotElements += subElement
2197        elif isinstance(subElement, articulations.Articulation):
2198            theNote.articulations.append(subElement)
2199        elif isinstance(subElement, str):
2200            theNote.pitch.accidental = pitch.Accidental(subElement)
2201        elif isinstance(subElement, note.Lyric):
2202            theNote.lyrics = [subElement]
2203
2204    # adjust for @accid.ges if present
2205    if elem.get('accid.ges') is not None:
2206        theNote.pitch.accidental = pitch.Accidental(_accidGesFromAttr(elem.get('accid.ges', '')))
2207
2208    # we can only process slurs if we got a SpannerBundle as the "slurBundle" argument
2209    if slurBundle is not None:
2210        addSlurs(elem, theNote, slurBundle)
2211
2212    # id in the @xml:id attribute
2213    if elem.get(_XMLID) is not None:
2214        theNote.id = elem.get(_XMLID)
2215
2216    # articulations in the @artic attribute
2217    if elem.get('artic') is not None:
2218        theNote.articulations.extend(_makeArticList(elem.get('artic')))
2219
2220    # ties in the @tie attribute
2221    if elem.get('tie') is not None:
2222        theNote.tie = _tieFromAttr(elem.get('tie'))
2223
2224    # dots from inner <dot> elements
2225    if dotElements > 0:
2226        theNote.duration = makeDuration(_qlDurationFromAttr(elem.get('dur')), dotElements)
2227
2228    # grace note (only mark as grace note---don't worry about "time-stealing")
2229    if elem.get('grace') is not None:
2230        theNote.duration = duration.GraceDuration(theNote.duration.quarterLength)
2231
2232    # beams indicated by a <beamSpan> held elsewhere
2233    if elem.get('m21Beam') is not None:
2234        if duration.convertTypeToNumber(theNote.duration.type) > 4:
2235            theNote.beams.fill(theNote.duration.type, elem.get('m21Beam'))
2236
2237    # tuplets
2238    if elem.get('m21TupletNum') is not None:
2239        theNote = scaleToTuplet(theNote, elem)
2240
2241    # lyrics indicated with <verse>
2242    if elem.find(f'./{MEI_NS}verse') is not None:
2243        tempLyrics = []
2244        for i, eachVerse in enumerate(elem.findall(f'./{MEI_NS}verse')):
2245            tempLyrics.extend(verseFromElement(eachVerse, backupN=i + 1))
2246        theNote.lyrics = tempLyrics
2247
2248    return theNote
2249
2250
2251def restFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
2252    '''
2253    <rest/> is a non-sounding event found in the source being transcribed
2254
2255    In MEI 2013: pg.424 (438 in PDF) (MEI.shared module)
2256
2257    **Attributes/Elements Implemented:**
2258
2259    - xml:id (or id), an XML id (submitted as the Music21Object "id")
2260    - dur, from att.duration.musical: (via _qlDurationFromAttr())
2261    - dots, from att.augmentdots: [0..4]
2262
2263    **Attributes/Elements in Testing:** none
2264
2265    **Attributes not Implemented:**
2266
2267    - att.common (@label, @n, @xml:base)
2268    - att.facsimile (@facs)
2269    - att.rest.log
2270
2271        - (att.event
2272
2273            - (att.timestamp.musical (@tstamp))
2274            - (att.timestamp.performed (@tstamp.ges, @tstamp.real))
2275            - (att.staffident (@staff))
2276            - (att.layerident (@layer)))
2277
2278        - (att.fermatapresent (@fermata))
2279
2280            - (att.tupletpresent (@tuplet))
2281            - (att.rest.log.cmn (att.beamed (@beam)))
2282
2283    - att.rest.vis (all)
2284    - att.rest.ges (all)
2285    - att.rest.anl (all)
2286
2287    **Contained Elements not Implemented:** none
2288    '''
2289    # NOTE: keep this in sync with spaceFromElement()
2290
2291    theDuration = _qlDurationFromAttr(elem.get('dur'))
2292    theDuration = makeDuration(theDuration, int(elem.get('dots', 0)))
2293    theRest = note.Rest(duration=theDuration)
2294
2295    if elem.get(_XMLID) is not None:
2296        theRest.id = elem.get(_XMLID)
2297
2298    # tuplets
2299    if elem.get('m21TupletNum') is not None:
2300        theRest = scaleToTuplet(theRest, elem)
2301
2302    return theRest
2303
2304
2305def mRestFromElement(elem, slurBundle=None):
2306    '''
2307    <mRest/> Complete measure rest in any meter.
2308
2309    In MEI 2013: pg.375 (389 in PDF) (MEI.cmn module)
2310
2311    This is a function wrapper for :func:`restFromElement`.
2312
2313    .. note:: If the <mRest> element does not have a @dur attribute, it will have the default
2314        duration of 1.0. This must be fixed later, so the :class:`Rest` object returned from this
2315        method is given the :attr:`m21wasMRest` attribute, set to True.
2316    '''
2317    # NOTE: keep this in sync with mSpaceFromElement()
2318
2319    if elem.get('dur') is not None:
2320        return restFromElement(elem, slurBundle)
2321    else:
2322        theRest = restFromElement(elem, slurBundle)
2323        theRest.m21wasMRest = True
2324        return theRest
2325
2326
2327def spaceFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
2328    '''
2329    <space>  A placeholder used to fill an incomplete measure, layer, etc. most often so that the
2330    combined duration of the events equals the number of beats in the measure.
2331
2332    Returns a Rest element with hideObjectOnPrint = True
2333
2334    In MEI 2013: pg.440 (455 in PDF) (MEI.shared module)
2335    '''
2336    # NOTE: keep this in sync with restFromElement()
2337
2338    theDuration = _qlDurationFromAttr(elem.get('dur'))
2339    theDuration = makeDuration(theDuration, int(elem.get('dots', 0)))
2340    theSpace = note.Rest(duration=theDuration)
2341    theSpace.style.hideObjectOnPrint = True
2342
2343    if elem.get(_XMLID) is not None:
2344        theSpace.id = elem.get(_XMLID)
2345
2346    # tuplets
2347    if elem.get('m21TupletNum') is not None:
2348        theSpace = scaleToTuplet(theSpace, elem)
2349
2350    return theSpace
2351
2352
2353def mSpaceFromElement(elem, slurBundle=None):
2354    '''
2355    <mSpace/> A measure containing only empty space in any meter.
2356
2357    In MEI 2013: pg.377 (391 in PDF) (MEI.cmn module)
2358
2359    This is a function wrapper for :func:`spaceFromElement`.
2360
2361    .. note:: If the <mSpace> element does not have a @dur attribute, it will have the default
2362        duration of 1.0. This must be fixed later, so the :class:`Space` object returned from this
2363        method is given the :attr:`m21wasMRest` attribute, set to True.
2364    '''
2365    # NOTE: keep this in sync with mRestFromElement()
2366
2367    if elem.get('dur') is not None:
2368        return spaceFromElement(elem, slurBundle)
2369    else:
2370        theSpace = spaceFromElement(elem, slurBundle)
2371        theSpace.m21wasMRest = True
2372        return theSpace
2373
2374
2375def chordFromElement(elem, slurBundle=None):
2376    # NOTE: this function should stay in sync with noteFromElement() where sensible
2377    '''
2378    <chord> is a simultaneous sounding of two or
2379    more notes in the same layer with the same duration.
2380
2381    In MEI 2013: pg.280 (294 in PDF) (MEI.shared module)
2382
2383    **Attributes/Elements Implemented:**
2384
2385    - @xml:id (or id), an XML id (submitted as the Music21Object "id")
2386    - <note> contained within
2387    - @dur, from att.duration.musical: (via _qlDurationFromAttr())
2388    - @dots, from att.augmentdots: [0..4]
2389    - @artic and <artic>
2390    - @tie, (many of "[i|m|t]")
2391    - @slur, (many of "[i|m|t][1-6]")
2392    - @grace, from att.note.ges.cmn: partial implementation (notes marked as grace, but the
2393        duration is 0 because we ignore the question of which neighbouring note to borrow time from)
2394
2395    **Attributes/Elements in Testing:** none
2396
2397    **Attributes not Implemented:**
2398
2399    - att.common (@label, @n, @xml:base)
2400    - att.facsimile (@facs)
2401    - att.chord.log
2402
2403        - (att.event
2404
2405            - (att.timestamp.musical (@tstamp))
2406            - (att.timestamp.performed (@tstamp.ges, @tstamp.real))
2407            - (att.staffident (@staff))
2408            - (att.layerident (@layer)))
2409
2410        - (att.fermatapresent (@fermata))
2411        - (att.syltext (@syl))
2412        - (att.chord.log.cmn
2413
2414            - (att.tupletpresent (@tuplet))
2415            - (att.beamed (@beam))
2416            - (att.lvpresent (@lv))
2417            - (att.ornam (@ornam)))
2418
2419    - att.chord.vis (all)
2420    - att.chord.ges
2421
2422        - (att.articulation.performed (@artic.ges))
2423        - (att.duration.performed (@dur.ges))
2424        - (att.instrumentident (@instr))
2425        - (att.chord.ges.cmn (att.graced (@grace, @grace.time)))  <-- partially implemented
2426
2427    - att.chord.anl (all)
2428
2429    **Contained Elements not Implemented:**
2430
2431    - MEI.edittrans: (all)
2432    '''
2433    tagToFunction = {f'{MEI_NS}note': lambda *x: None,
2434                     f'{MEI_NS}artic': articFromElement}
2435
2436    # start with a Chord with a bunch of Notes
2437    theChord = []
2438    for eachNote in elem.iterfind(f'{MEI_NS}note'):
2439        theChord.append(noteFromElement(eachNote, slurBundle))
2440    theChord = chord.Chord(notes=theChord)
2441
2442    # set the Chord's duration
2443    theDuration = _qlDurationFromAttr(elem.get('dur'))
2444    theDuration = makeDuration(theDuration, int(elem.get('dots', 0)))
2445    theChord.duration = theDuration
2446
2447    # iterate all immediate children
2448    for subElement in _processEmbeddedElements(elem.findall('*'),
2449                                               tagToFunction,
2450                                               elem.tag,
2451                                               slurBundle):
2452        if isinstance(subElement, articulations.Articulation):
2453            theChord.articulations.append(subElement)
2454
2455    # we can only process slurs if we got a SpannerBundle as the "slurBundle" argument
2456    if slurBundle is not None:
2457        addSlurs(elem, theChord, slurBundle)
2458
2459    # id in the @xml:id attribute
2460    if elem.get(_XMLID) is not None:
2461        theChord.id = elem.get(_XMLID)
2462
2463    # articulations in the @artic attribute
2464    if elem.get('artic') is not None:
2465        theChord.articulations.extend(_makeArticList(elem.get('artic')))
2466
2467    # ties in the @tie attribute
2468    if elem.get('tie') is not None:
2469        theChord.tie = _tieFromAttr(elem.get('tie'))
2470
2471    # grace note (only mark as grace note---don't worry about "time-stealing")
2472    if elem.get('grace') is not None:
2473        theChord.duration = duration.GraceDuration(theChord.duration.quarterLength)
2474
2475    # beams indicated by a <beamSpan> held elsewhere
2476    if elem.get('m21Beam') is not None:
2477        if duration.convertTypeToNumber(theChord.duration.type) > 4:
2478            theChord.beams.fill(theChord.duration.type, elem.get('m21Beam'))
2479
2480    # tuplets
2481    if elem.get('m21TupletNum') is not None:
2482        theChord = scaleToTuplet(theChord, elem)
2483
2484    return theChord
2485
2486
2487def clefFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
2488    '''
2489    <clef> Indication of the exact location of a particular note on the staff and, therefore,
2490    the other notes as well.
2491
2492    In MEI 2013: pg.284 (298 in PDF) (MEI.shared module)
2493
2494    **Attributes/Elements Implemented:**
2495
2496    - @xml:id (or id), an XML id (submitted as the Music21Object "id")
2497    - @shape, from att.clef.gesatt.clef.log
2498    - @line, from att.clef.gesatt.clef.log
2499    - @dis, from att.clef.gesatt.clef.log
2500    - @dis.place, from att.clef.gesatt.clef.log
2501
2502    **Attributes/Elements Ignored:**
2503
2504    - @cautionary, since this has no obvious implication for a music21 Clef
2505    - @octave, since this is likely obscure
2506
2507    **Attributes/Elements in Testing:** none
2508
2509    **Attributes not Implemented:**
2510
2511    - att.common (@label, @n, @xml:base)
2512    - att.event
2513
2514        - (att.timestamp.musical (@tstamp))
2515        - (att.timestamp.performed (@tstamp.ges, @tstamp.real))
2516        - (att.staffident (@staff))
2517        - (att.layerident (@layer))
2518
2519    - att.facsimile (@facs)
2520    - att.clef.anl (all)
2521    - att.clef.vis (all)
2522
2523    **Contained Elements not Implemented:** none
2524    '''
2525    if 'perc' == elem.get('shape'):
2526        theClef = clef.PercussionClef()
2527    elif 'TAB' == elem.get('shape'):
2528        theClef = clef.TabClef()
2529    else:
2530        theClef = clef.clefFromString(elem.get('shape') + elem.get('line'),
2531                                      octaveShift=_getOctaveShift(elem.get('dis'),
2532                                                                  elem.get('dis.place')))
2533
2534    if elem.get(_XMLID) is not None:
2535        theClef.id = elem.get(_XMLID)
2536
2537    return theClef
2538
2539
2540def instrDefFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
2541    # TODO: robuster handling of <instrDef>, including <instrGrp> and if held in a <staffGrp>
2542    '''
2543    <instrDef> (instrument definition)---MIDI instrument declaration.
2544
2545    In MEI 2013: pg.344 (358 in PDF) (MEI.midi module)
2546
2547    :returns: An :class:`Instrument`
2548
2549    **Attributes/Elements Implemented:**
2550
2551    - @midi.instrname (att.midiinstrument)
2552    - @midi.instrnum (att.midiinstrument)
2553
2554    **Attributes/Elements in Testing:** none
2555
2556    **Attributes/Elements Ignored:**
2557
2558    - @xml:id
2559
2560    **Attributes not Implemented:**
2561
2562    - att.common (@label, @n, @xml:base)
2563    - att.channelized (@midi.channel, @midi.duty, @midi.port, @midi.track)
2564    - att.midiinstrument (@midi.pan, @midi.volume)
2565
2566    **Contained Elements not Implemented:** none
2567    '''
2568    if elem.get('midi.instrnum') is not None:
2569        return instrument.instrumentFromMidiProgram(int(elem.get('midi.instrnum')))
2570    else:
2571        try:
2572            return instrument.fromString(elem.get('midi.instrname'))
2573        except (AttributeError, instrument.InstrumentException):
2574            theInstr = instrument.Instrument()
2575            theInstr.partName = elem.get('midi.instrname', '')
2576            return theInstr
2577
2578
2579def beamFromElement(elem, slurBundle=None):
2580    '''
2581    <beam> A container for a series of explicitly beamed events that begins and ends entirely
2582           within a measure.
2583
2584    In MEI 2013: pg.264 (278 in PDF) (MEI.cmn module)
2585
2586    :param elem: The ``<beam>`` element to process.
2587    :type elem: :class:`~xml.etree.ElementTree.Element`
2588    :returns: An iterable of all the objects contained within the ``<beam>`` container.
2589    :rtype: list of :class:`~music21.base.Music21Object`
2590
2591    **Example**
2592
2593    Here, three :class:`Note` objects are beamed together. Take note that the function returns
2594    a list of three objects, none of which is a :class:`Beam` or similar.
2595
2596    >>> from xml.etree import ElementTree as ET
2597    >>> from music21 import *
2598    >>> meiSnippet = """<beam xmlns="http://www.music-encoding.org/ns/mei">
2599    ...     <note pname='A' oct='7' dur='8'/>
2600    ...     <note pname='B' oct='7' dur='8'/>
2601    ...     <note pname='C' oct='6' dur='8'/>
2602    ... </beam>"""
2603    >>> meiSnippet = ET.fromstring(meiSnippet)
2604    >>> result = mei.base.beamFromElement(meiSnippet)
2605    >>> isinstance(result, list)
2606    True
2607    >>> len(result)
2608    3
2609    >>> result[0].pitch.nameWithOctave
2610    'A7'
2611    >>> result[0].beams
2612    <music21.beam.Beams <music21.beam.Beam 1/start>>
2613    >>> result[1].pitch.nameWithOctave
2614    'B7'
2615    >>> result[1].beams
2616    <music21.beam.Beams <music21.beam.Beam 1/continue>>
2617    >>> result[2].pitch.nameWithOctave
2618    'C6'
2619    >>> result[2].beams
2620    <music21.beam.Beams <music21.beam.Beam 1/stop>>
2621
2622    **Attributes/Elements Implemented:**
2623
2624    - <clef>, <chord>, <note>, <rest>, <space>, <tuplet>, <beam>, <barLine>
2625
2626    **Attributes/Elements Ignored:**
2627
2628    - @xml:id
2629
2630    **Attributes/Elements in Testing:** none
2631
2632    **Attributes not Implemented:**
2633
2634    - att.common (@label, @n, @xml:base)
2635    - att.facsimile (@facs)
2636    - att.beam.log
2637
2638        - (att.event
2639
2640            - (att.timestamp.musical (@tstamp))
2641            - (att.timestamp.performed (@tstamp.ges, @tstamp.real))
2642            - (att.staffident (@staff))
2643            - (att.layerident (@layer)))
2644
2645        - (att.beamedwith (@beam.with))
2646
2647    - att.beam.vis (all)
2648    - att.beam.gesatt.beam.anl (all)
2649
2650    **Contained Elements not Implemented:**
2651
2652    - MEI.cmn: bTrem beatRpt fTrem halfmRpt meterSig meterSigGrp
2653    - MEI.critapp: app
2654    - MEI.edittrans: (all)
2655    - MEI.mensural: ligature mensur proport
2656    - MEI.shared: clefGrp custos keySig pad
2657    '''
2658    # NB: The doctest is a sufficient integration test. Since there is no logic, I don't think we
2659    #     need to bother with unit testing.
2660
2661    # mapping from tag name to our converter function
2662    tagToFunction = {
2663        f'{MEI_NS}clef': clefFromElement,
2664        f'{MEI_NS}chord': chordFromElement,
2665        f'{MEI_NS}note': noteFromElement,
2666        f'{MEI_NS}rest': restFromElement,
2667        f'{MEI_NS}tuplet': tupletFromElement,
2668        f'{MEI_NS}beam': beamFromElement,
2669        f'{MEI_NS}space': spaceFromElement,
2670        f'{MEI_NS}barLine': barLineFromElement,
2671    }
2672
2673    beamedStuff = _processEmbeddedElements(elem.findall('*'), tagToFunction, elem.tag, slurBundle)
2674    beamedStuff = beamTogether(beamedStuff)
2675
2676    return beamedStuff
2677
2678
2679def barLineFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
2680    '''
2681    <barLine> Vertical line drawn through one or more staves that divides musical notation into
2682    metrical units.
2683
2684    In MEI 2013: pg.262 (276 in PDF) (MEI.shared module)
2685
2686    :returns: A :class:`music21.bar.Barline` or :class:`~music21.bar.Repeat`, depending on the
2687        value of @rend. If @rend is ``'rptboth'``, a 2-tuplet of :class:`Repeat` objects will be
2688        returned, represented an "end" and "start" barline, as specified in the :mod:`music21.bar`
2689        documentation.
2690
2691    .. note:: The music21-to-other converters expect that a :class:`Barline` will be attached to a
2692        :class:`Measure`, which it will not be when imported from MEI as a <barLine> element.
2693        However, this function does import correctly to a :class:`Barline` that you can access from
2694        Python in the :class:`Stream` object as expected.
2695
2696    **Attributes/Elements Implemented:**
2697
2698    - @rend from att.barLine.log
2699
2700    **Attributes/Elements in Testing:** none
2701
2702    **Attributes not Implemented:**
2703
2704    - att.common (@label, @n, @xml:base) (att.id (@xml:id))
2705    - att.facsimile (@facs)
2706    - att.pointing (@xlink:actuate, @xlink:role, @xlink:show, @target, @targettype, @xlink:title)
2707    - att.barLine.log
2708
2709        - (att.meterconformance.bar (@metcon, @control))
2710
2711    - att.barLine.vis
2712
2713        - (att.barplacement (@barplace, @taktplace))
2714        - (att.color (@color))
2715        - (att.measurement (@unit))
2716        - (att.width (@width))
2717
2718    - att.barLine.ges (att.timestamp.musical (@tstamp))
2719    - att.barLine.anl
2720
2721        - (att.common.anl
2722
2723            - (@copyof, @corresp, @next, @prev, @sameas, @synch)
2724            - (att.alignment (@when)))
2725
2726    **Contained Elements not Implemented:** none
2727    '''
2728    return _barlineFromAttr(elem.get('rend', 'single'))
2729
2730
2731def tupletFromElement(elem, slurBundle=None):
2732    '''
2733    <tuplet> A group of notes with "irregular" (sometimes called "irrational") rhythmic values,
2734    for example, three notes in the time normally occupied by two or nine in the time of five.
2735
2736    In MEI 2013: pg.473 (487 in PDF) (MEI.cmn module)
2737
2738    :param elem: The ``<tuplet>`` element to process.
2739    :type elem: :class:`~xml.etree.ElementTree.Element`
2740    :returns: An iterable of all the objects contained within the ``<tuplet>`` container.
2741    :rtype: tuple of :class:`~music21.base.Music21Object`
2742
2743    **Attributes/Elements Implemented:**
2744
2745    - <tuplet>, <beam>, <note>, <rest>, <chord>, <clef>, <space>, <barLine>
2746    - @num and @numbase
2747
2748    **Attributes/Elements in Testing:** none
2749
2750    **Attributes not Implemented:**
2751
2752    - att.common (@label, @n, @xml:base) (att.id (@xml:id))
2753    - att.facsimile (@facs)
2754    - att.tuplet.log
2755
2756        - (att.event
2757
2758            - (att.timestamp.musical (@tstamp))
2759            - (att.timestamp.performed (@tstamp.ges, @tstamp.real))
2760            - (att.staffident (@staff))
2761            - (att.layerident (@layer)))
2762
2763        - (att.beamedwith (@beam.with))
2764        - (att.augmentdots (@dots))
2765        - (att.duration.additive (@dur))
2766        - (att.startendid (@endid) (att.startid (@startid)))
2767
2768    - att.tuplet.vis (all)
2769    - att.tuplet.ges (att.duration.performed (@dur.ges))
2770    - att.tuplet.anl (all)
2771
2772    **Contained Elements not Implemented:**
2773
2774    - MEI.cmn: bTrem beatRpt fTrem halfmRpt meterSig meterSigGrp
2775    - MEI.critapp: app
2776    - MEI.edittrans: (all)
2777    - MEI.mensural: ligature mensur proport
2778    - MEI.shared: clefGrp custos keySig pad
2779    '''
2780    # mapping from tag name to our converter function
2781    tagToFunction = {
2782        f'{MEI_NS}tuplet': tupletFromElement,
2783        f'{MEI_NS}beam': beamFromElement,
2784        f'{MEI_NS}note': noteFromElement,
2785        f'{MEI_NS}rest': restFromElement,
2786        f'{MEI_NS}chord': chordFromElement,
2787        f'{MEI_NS}clef': clefFromElement,
2788        f'{MEI_NS}space': spaceFromElement,
2789        f'{MEI_NS}barLine': barLineFromElement,
2790    }
2791
2792    # get the @num and @numbase attributes, without which we can't properly calculate the tuplet
2793    if elem.get('num') is None or elem.get('numbase') is None:
2794        raise MeiAttributeError(_MISSING_TUPLET_DATA)
2795
2796    # iterate all immediate children
2797    tupletMembers = _processEmbeddedElements(elem.findall('*'), tagToFunction, elem.tag, slurBundle)
2798
2799    # "tuplet-ify" the duration of everything held within
2800    newElem = Element('c', m21TupletNum=elem.get('num'), m21TupletNumbase=elem.get('numbase'))
2801    tupletMembers = scaleToTuplet(tupletMembers, newElem)
2802
2803    # Set the Tuplet.type property for the first and final note in a tuplet.
2804    # We have to find the first and last duration-having thing, not just the first and last objects
2805    # between the <tuplet> tags.
2806    firstNote = None
2807    lastNote = None
2808    for i, eachObj in enumerate(tupletMembers):
2809        if firstNote is None and isinstance(eachObj, note.GeneralNote):
2810            firstNote = i
2811        elif isinstance(eachObj, note.GeneralNote):
2812            lastNote = i
2813
2814    tupletMembers[firstNote].duration.tuplets[0].type = 'start'
2815    if lastNote is None:
2816        # when there is only one object in the tuplet
2817        tupletMembers[firstNote].duration.tuplets[0].type = 'stop'
2818    else:
2819        tupletMembers[lastNote].duration.tuplets[0].type = 'stop'
2820
2821    # beam it all together
2822    tupletMembers = beamTogether(tupletMembers)
2823
2824    return tuple(tupletMembers)
2825
2826
2827def layerFromElement(elem, overrideN=None, slurBundle=None):
2828    '''
2829    <layer> An independent stream of events on a staff.
2830
2831    In MEI 2013: pg.353 (367 in PDF) (MEI.shared module)
2832
2833    .. note:: The :class:`Voice` object's :attr:`~music21.stream.Voice.id` attribute must be set
2834        properly in order to ensure continuity of voices between measures. If the ``elem`` does not
2835        have an @n attribute, you can set one with the ``overrideN`` parameter in this function. If
2836        you provide a value for ``overrideN``, it will be used instead of the ``elemn`` object's
2837        @n attribute.
2838
2839        Because improperly-set :attr:`~music21.stream.Voice.id` attributes nearly guarantees errors
2840        in the imported :class:`Score`, either ``overrideN`` or @n must be specified.
2841
2842    :param elem: The ``<layer>`` element to process.
2843    :type elem: :class:`~xml.etree.ElementTree.Element`
2844    :param str overrideN: The value to be set as the ``id``
2845        attribute in the outputted :class:`Voice`.
2846    :returns: A :class:`Voice` with the objects found in the provided :class:`Element`.
2847    :rtype: :class:`music21.stream.Voice`
2848    :raises: :exc:`MeiAttributeError` if neither ``overrideN`` nor @n are specified.
2849
2850    **Attributes/Elements Implemented:**
2851
2852    - <clef>, <chord>, <note>, <rest>, <mRest>, <beam>, <tuplet>, <space>, <mSpace> , and
2853      <barLine> contained within
2854    - @n, from att.common
2855
2856    **Attributes Ignored:**
2857
2858    - @xml:id
2859
2860    **Attributes/Elements in Testing:** none
2861
2862    **Attributes not Implemented:**
2863
2864    - att.common (@label, @xml:base)
2865    - att.declaring (@decls)
2866    - att.facsimile (@facs)
2867    - att.layer.log (@def) and (att.meterconformance (@metcon))
2868    - att.layer.vis (att.visibility (@visible))
2869    - att.layer.gesatt.layer.anl (all)
2870
2871    **Contained Elements not Implemented:**
2872
2873    - MEI.cmn: arpeg bTrem beamSpan beatRpt bend breath fTrem fermata gliss hairpin halfmRpt
2874               harpPedal mRpt mRpt2 meterSig meterSigGrp multiRest multiRpt octave pedal
2875               reh slur tie tuplet tupletSpan
2876    - MEI.cmnOrnaments: mordent trill turn
2877    - MEI.critapp: app
2878    - MEI.edittrans: (all)
2879    - MEI.harmony: harm
2880    - MEI.lyrics: lyrics
2881    - MEI.mensural: ligature mensur proport
2882    - MEI.midi: midi
2883    - MEI.neumes: ineume syllable uneume
2884    - MEI.shared: accid annot artic barLine clefGrp custos dir dot dynam keySig pad pb phrase sb
2885                  scoreDef staffDef tempo
2886    - MEI.text: div
2887    - MEI.usersymbols: anchoredText curve line symbol
2888    '''
2889    # mapping from tag name to our converter function
2890    tagToFunction = {
2891        f'{MEI_NS}clef': clefFromElement,
2892        f'{MEI_NS}chord': chordFromElement,
2893        f'{MEI_NS}note': noteFromElement,
2894        f'{MEI_NS}rest': restFromElement,
2895        f'{MEI_NS}mRest': mRestFromElement,
2896        f'{MEI_NS}beam': beamFromElement,
2897        f'{MEI_NS}tuplet': tupletFromElement,
2898        f'{MEI_NS}space': spaceFromElement,
2899        f'{MEI_NS}mSpace': mSpaceFromElement,
2900        f'{MEI_NS}barLine': barLineFromElement,
2901    }
2902
2903    # iterate all immediate children
2904    theLayer = _processEmbeddedElements(elem.iterfind('*'), tagToFunction, elem.tag, slurBundle)
2905
2906    # adjust the <layer>'s elements for possible tuplets
2907    theLayer = _guessTuplets(theLayer)
2908
2909    # make the Voice
2910    theVoice = stream.Voice()
2911    for each in theLayer:
2912        theVoice.coreAppend(each)
2913    theVoice.coreElementsChanged()
2914
2915    # try to set the Voice's "id" attribute
2916    if overrideN:
2917        theVoice.id = overrideN
2918    elif elem.get('n') is not None:
2919        theVoice.id = elem.get('n')
2920    else:
2921        raise MeiAttributeError(_MISSING_VOICE_ID)
2922
2923    return theVoice
2924
2925
2926def staffFromElement(elem, slurBundle=None):
2927    '''
2928    <staff> A group of equidistant horizontal lines on which notes are placed in order to
2929    represent pitch or a grouping element for individual 'strands' of notes, rests, etc. that may
2930    or may not actually be rendered on staff lines; that is, both diastematic and non-diastematic
2931    signs.
2932
2933    In MEI 2013: pg.444 (458 in PDF) (MEI.shared module)
2934
2935    :param elem: The ``<staff>`` element to process.
2936    :type elem: :class:`~xml.etree.ElementTree.Element`
2937    :returns: The :class:`Voice` classes corresponding to the ``<layer>`` tags in ``elem``.
2938    :rtype: list of :class:`music21.stream.Voice`
2939
2940    **Attributes/Elements Implemented:**
2941
2942    - <layer> contained within
2943
2944    **Attributes Ignored:**
2945
2946    - @xml:id
2947
2948    **Attributes/Elements in Testing:** none
2949
2950    **Attributes not Implemented:**
2951
2952    - att.common (@label, @n, @xml:base)
2953    - att.declaring (@decls)
2954    - att.facsimile (@facs)
2955    - att.staff.log (@def) (att.meterconformance (@metcon))
2956    - att.staff.vis (att.visibility (@visible))
2957    - att.staff.gesatt.staff.anl (all)
2958
2959    **Contained Elements not Implemented:**
2960
2961    - MEI.cmn: ossia
2962    - MEI.critapp: app
2963    - MEI.edittrans: (all)
2964    - MEI.shared: annot pb sb scoreDef staffDef
2965    - MEI.text: div
2966    - MEI.usersymbols: anchoredText curve line symbol
2967    '''
2968    # mapping from tag name to our converter function
2969    layerTagName = f'{MEI_NS}layer'
2970    tagToFunction = {}
2971    layers = []
2972
2973    # track the @n values given to layerFromElement()
2974    currentNValue = '1'
2975
2976    # iterate all immediate children
2977    for eachTag in elem.iterfind('*'):
2978        if layerTagName == eachTag.tag:
2979            layers.append(layerFromElement(eachTag, currentNValue, slurBundle=slurBundle))
2980            currentNValue = f'{int(currentNValue) + 1}'  # inefficient, but we need a string
2981        elif eachTag.tag in tagToFunction:
2982            # NB: this won't be tested until there's something in tagToFunction
2983            layers.append(tagToFunction[eachTag.tag](eachTag, slurBundle))
2984        elif eachTag.tag not in _IGNORE_UNPROCESSED:
2985            environLocal.printDebug(_UNPROCESSED_SUBELEMENT.format(eachTag.tag, elem.tag))
2986
2987    return layers
2988
2989
2990def _correctMRestDurs(staves, targetLength):
2991    '''
2992    Helper function for measureFromElement(), not intended to be used elsewhere. It's a separate
2993    function only (1) to reduce duplication, and (2) to improve testability.
2994
2995    Iterate the imported objects of <layer> elements in the <staff> elements in a <measure>,
2996    detecting those with the "m21wasMRest" attribute and setting their duration to "targetLength."
2997
2998    The "staves" argument should be a dictionary where the values are Measure objects with at least
2999    one Voice object inside.
3000
3001    The "targetLength" argument should be the duration of the measure.
3002
3003    Nothing is returned; the duration of affected objects is modified in-place.
3004    '''
3005    for eachMeasure in staves.values():
3006        for eachVoice in eachMeasure:
3007            if not isinstance(eachVoice, stream.Stream):
3008                continue
3009            for eachObject in eachVoice:
3010                if hasattr(eachObject, 'm21wasMRest'):
3011                    eachObject.quarterLength = targetLength
3012                    eachVoice.duration = duration.Duration(targetLength)
3013                    eachMeasure.duration = duration.Duration(targetLength)
3014                    del eachObject.m21wasMRest
3015
3016
3017def _makeBarlines(elem, staves):
3018    '''
3019    This is a helper function for :func:`measureFromElement`, made independent only to improve
3020    that function's ease of testing.
3021
3022    Given a <measure> element and a dictionary with the :class:`Measure` objects that have already
3023    been processed, change the barlines of the :class:`Measure` objects in accordance with the
3024    element's @left and @right attributes.
3025
3026    :param :class:`~xml.etree.ElementTree.Element` elem: The ``<measure>`` tag to process.
3027    :param dict staves: Dictionary where keys are @n attributes and values are corresponding
3028        :class:`~music21.stream.Measure` objects.
3029    :returns: The ``staves`` dictionary with properly-set barlines.
3030    :rtype: dict
3031    '''
3032    if elem.get('left') is not None:
3033        bars = _barlineFromAttr(elem.get('left'))
3034        if hasattr(bars, '__len__'):
3035            # this means @left was "rptboth"
3036            bars = bars[1]
3037        for eachMeasure in staves.values():
3038            if isinstance(eachMeasure, stream.Measure):
3039                eachMeasure.leftBarline = bars
3040
3041    if elem.get('right') is not None:
3042        bars = _barlineFromAttr(elem.get('right'))
3043        if hasattr(bars, '__len__'):
3044            # this means @right was "rptboth"
3045            staves['next @left'] = bars[1]
3046            bars = bars[0]
3047        for eachMeasure in staves.values():
3048            if isinstance(eachMeasure, stream.Measure):
3049                eachMeasure.rightBarline = bars
3050
3051    return staves
3052
3053
3054def measureFromElement(elem, backupNum, expectedNs, slurBundle=None, activeMeter=None):
3055    '''
3056    <measure> Unit of musical time consisting of a fixed number of note-values of a given type, as
3057    determined by the prevailing meter, and delimited in musical notation by two bar lines.
3058
3059    In MEI 2013: pg.365 (379 in PDF) (MEI.cmn module)
3060
3061    :param elem: The ``<measure>`` element to process.
3062    :type elem: :class:`~xml.etree.ElementTree.Element`
3063    :param int backupNum: A fallback value for the resulting
3064        :class:`~music21.stream.Measure` objects' number attribute.
3065    :param expectedNs: A list of the expected @n attributes for the <staff> tags in this <measure>.
3066        If an expected <staff> isn't in the <measure>, it will be created with a full-measure rest.
3067    :type expectedNs: iterable of str
3068    :param activeMeter: The :class:`~music21.meter.TimeSignature` active in this <measure>. This is
3069        used to adjust the duration of an <mRest> that was given without a @dur attribute.
3070    :returns: A dictionary where keys are the @n attributes for <staff> tags found in this
3071        <measure>, and values are :class:`~music21.stream.Measure` objects that should be appended
3072        to the :class:`~music21.stream.Part` instance with the value's @n attributes.
3073    :rtype: dict of :class:`~music21.stream.Measure`
3074
3075    .. note:: When the right barline is set to ``'rptboth'`` in MEI, it requires adjusting the left
3076        barline of the following <measure>. If this happens, the :class:`Repeat` object is assigned
3077        to the ``'next @left'`` key in the returned dictionary.
3078
3079    **Attributes/Elements Implemented:**
3080
3081    - contained elements: <staff> and <staffDef>
3082    - @right and @left (att.measure.log)
3083    - @n (att.common)
3084
3085    **Attributes Ignored:**
3086
3087    - @xml:id (att.id)
3088    - <slur> and <tie> contained within. These spanners will usually be attached to their starting
3089      and ending notes with @xml:id attributes, so it's not necessary to process them when
3090      encountered in a <measure>. Furthermore, because the possibility exists for cross-measure
3091      slurs and ties, we can't guarantee we'll be able to process all spanners until all
3092      spanner-attachable objects are processed. So we manage these tags at a higher level.
3093
3094    **Attributes/Elements in Testing:** none
3095
3096    **Attributes not Implemented:**
3097
3098    - att.common (@label, @xml:base)
3099    - att.declaring (@decls)
3100    - att.facsimile (@facs)
3101    - att.typed (@type, @subtype)
3102    - att.pointing (@xlink:actuate, @xlink:role, @xlink:show, @target, @targettype, @xlink:title)
3103    - att.measure.log (att.meterconformance.bar (@metcon, @control))
3104    - att.measure.vis (all)
3105    - att.measure.ges (att.timestamp.performed (@tstamp.ges, @tstamp.real))
3106    - att.measure.anl (all)
3107
3108    **Contained Elements not Implemented:**
3109
3110    - MEI.cmn: arpeg beamSpan bend breath fermata gliss hairpin harpPedal octave ossia pedal reh
3111               tupletSpan
3112    - MEI.cmnOrnaments: mordent trill turn
3113    - MEI.critapp: app
3114    - MEI.edittrans: add choice corr damage del gap handShift orig reg restore sic subst supplied
3115                     unclear
3116    - MEI.harmony: harm
3117    - MEI.lyrics: lyrics
3118    - MEI.midi: midi
3119    - MEI.shared: annot dir dynam pb phrase sb tempo
3120    - MEI.text: div
3121    - MEI.usersymbols: anchoredText curve line symbol
3122    '''
3123    staves = {}
3124    stavesWaiting = {}  # for staff-specific objects processed before the corresponding staff
3125
3126    # mapping from tag name to our converter function
3127    staffTag = f'{MEI_NS}staff'
3128    staffDefTag = f'{MEI_NS}staffDef'
3129
3130    # track the bar's duration
3131    maxBarDuration = None
3132
3133    # iterate all immediate children
3134    for eachElem in elem.iterfind('*'):
3135        if staffTag == eachElem.tag:
3136            staves[eachElem.get('n')] = stream.Measure(staffFromElement(eachElem,
3137                                                                        slurBundle=slurBundle),
3138                                                       number=int(elem.get('n', backupNum)))
3139            thisBarDuration = staves[eachElem.get('n')].duration.quarterLength
3140            if maxBarDuration is None or maxBarDuration < thisBarDuration:
3141                maxBarDuration = thisBarDuration
3142        elif staffDefTag == eachElem.tag:
3143            whichN = eachElem.get('n')
3144            if whichN is None:
3145                environLocal.warn(_UNIMPLEMENTED_IMPORT.format('<staffDef>', '@n'))
3146            else:
3147                stavesWaiting[whichN] = staffDefFromElement(eachElem, slurBundle)
3148        elif eachElem.tag not in _IGNORE_UNPROCESSED:
3149            environLocal.printDebug(_UNPROCESSED_SUBELEMENT.format(eachElem.tag, elem.tag))
3150
3151    # Process objects from a <staffDef>...
3152    # We must process them now because, if we did it in the loop above, the respective <staff> may
3153    # not be processed before the <staffDef>.
3154    for whichN, eachDict in stavesWaiting.items():
3155        for eachObj in eachDict.values():
3156            # We must insert() these objects because a <staffDef> signals its changes for the
3157            # *start* of the <measure> in which it appears.
3158            staves[whichN].insert(0, eachObj)
3159
3160    # create rest-filled measures for expected parts that had no <staff> tag in this <measure>
3161    for eachN in expectedNs:
3162        if eachN not in staves:
3163            restVoice = stream.Voice([note.Rest(quarterLength=maxBarDuration)])
3164            restVoice.id = '1'
3165            # just in case (e.g., when all the other voices are <mRest>)
3166            restVoice[0].m21wasMRest = True
3167            staves[eachN] = stream.Measure([restVoice], number=int(elem.get('n', backupNum)))
3168
3169    # First search for Rest objects created by an <mRest> element that didn't have @dur set. This
3170    # will only work in cases where not all of the parts are resting. However, it avoids a more
3171    # time-consuming search later.
3172    if (maxBarDuration == _DUR_ATTR_DICT[None]
3173            and activeMeter is not None
3174            and maxBarDuration != activeMeter.barDuration.quarterLength):
3175        # In this case, all the staves have <mRest> elements without a @dur.
3176        _correctMRestDurs(staves, activeMeter.barDuration.quarterLength)
3177    else:
3178        # In this case, some or none of the staves have an <mRest> element without a @dur.
3179        _correctMRestDurs(staves, maxBarDuration)
3180
3181    # assign left and right barlines
3182    staves = _makeBarlines(elem, staves)
3183
3184    return staves
3185
3186
3187def sectionScoreCore(elem, allPartNs, slurBundle, *,
3188                     activeMeter=None, nextMeasureLeft=None, backupMeasureNum=0):
3189    '''
3190    This function is the "core" of both :func:`sectionFromElement` and :func:`scoreFromElement`,
3191    since both elements are treated quite similarly (though not identically). It's a separate and
3192    shared function to reduce code duplication and
3193    increase ease of testing. It's a "public" function
3194    to help spread the burden of API documentation complexity: while the parameters and return
3195    values are described in this function, the compliance with the MEI Guidelines is described in
3196    both :func:`sectionFromElement` and :func:`scoreFromElement`, as expected.
3197
3198    **Required Parameters**
3199
3200    :param elem: The <section> or <score> element to process.
3201    :type elem: :class:`xml.etree.ElementTree.Element`
3202    :param allPartNs: A list or tuple of the expected @n attributes for the <staff> tags in this
3203        <section>. This tells the function how many parts there are and what @n values they use.
3204    :type allPartNs: iterable of str
3205    :param slurBundle: This :class:`SpannerBundle` holds the :class:`~music21.spanner.Slur` objects
3206        created during pre-processing. The slurs are attached to their respective :class:`Note` and
3207        :class:`Chord` objects as they are processed.
3208    :type slurBundle: :class:`music21.spanner.SpannerBundle`
3209
3210    **Optional Keyword Parameters**
3211
3212    The following parameters are all optional, and must be specified as a keyword argument (i.e.,
3213    you specify the parameter name before its value).
3214
3215    :param activeMeter: The :class:`~music21.meter.TimeSignature` active at the start of this
3216        <section> or <score>. This is updated automatically as the music is processed, and the
3217        :class:`TimeSignature` active at the end of the element is returned.
3218    :type activeMeter: :class:`music21.meter.TimeSignature`
3219    :param nextMeasureLeft: The @left attribute to use for the next <measure> element encountered.
3220        This is used for situations where one <measure> element specified a @right attribute that
3221        must be imported by music21 as *both* the right barline of one measure and the left barline
3222        of the following; at the moment this is only @rptboth, which requires a :class:`Repeat` in
3223        both cases.
3224    :type nextMeasureLeft: :class:`music21.bar.Barline` or :class:`music21.bar.Repeat`
3225    :param backupMeasureNum: In case a <measure> element is missing its @n attribute,
3226        :func:`measureFromElement` will use this automatically-incremented number instead. The
3227        ``backupMeasureNum`` corresponding to the final <measure> in this <score> or <section> is
3228        returned from this function.
3229    :type backupMeasureNum: int
3230    :returns: Four-tuple with a dictionary of results, the new value of ``activeMeter``, the new
3231        value of ``nextMeasureLeft``, and the new value of ``backupMeasureNum``.
3232    :rtype: (dict, :class:`~music21.meter.TimeSignature`, :class:`~music21.bar.Barline`, int)
3233
3234    **Return Value**
3235
3236    In short, it's ``parsed``, ``activeMeter``, ``nextMeasureLeft``, ``backupMeasureNum``.
3237
3238    - ``'parsed'`` is a dictionary where the keys are the values in ``allPartNs`` and the values are
3239        a list of all the :class:`Measure` objects in that part, as found in this <section> or
3240        <score>.
3241    - ``'activeMeter'`` is the :class:`~music21.meter.TimeSignature` in effect at the end of this
3242        <section> or <score>.
3243    - ``'nextMeasureLeft'`` is the value that should be
3244        assigned to the :attr:`leftBarline` attribute
3245        of the first :class:`Measure` found in the next <section>. This will almost always be None.
3246    - ``'backupMeasureNum'`` is equal to the ``backupMeasureNum`` argument plus the number of
3247        <measure> elements found in this <score> or <section>.
3248    '''
3249    # pylint: disable=too-many-nested-blocks
3250    # ^^^ -- was not required at time of contribution
3251
3252    # TODO: replace the returned 4-tuple with a namedtuple
3253
3254    # NOTE: "activeMeter" holds the TimeSignature object that's currently active; it's used in the
3255    # loop below to help determine the proper duration of a full-measure rest. It must persist
3256    # between <section> elements, so it's a parameter for this function.
3257
3258    scoreTag = f'{MEI_NS}score'
3259    sectionTag = f'{MEI_NS}section'
3260    measureTag = f'{MEI_NS}measure'
3261    scoreDefTag = f'{MEI_NS}scoreDef'
3262    staffDefTag = f'{MEI_NS}staffDef'
3263
3264    # hold the music21.stream.Part that we're building
3265    parsed = {n: [] for n in allPartNs}
3266    # hold things that belong in the following "Thing" (either Measure or Section)
3267    inNextThing = {n: [] for n in allPartNs}
3268
3269    for eachElem in elem.iterfind('*'):
3270        # only process <measure> elements if this is a <section>
3271        if measureTag == eachElem.tag and sectionTag == elem.tag:
3272            backupMeasureNum += 1
3273            # process all the stuff in the <measure>
3274            measureResult = measureFromElement(eachElem, backupMeasureNum, allPartNs,
3275                                               slurBundle=slurBundle,
3276                                               activeMeter=activeMeter)
3277            # process and append each part's stuff to the staff
3278            for eachN in allPartNs:
3279                # insert objects specified in the immediately-preceding <scoreDef>
3280                for eachThing in inNextThing[eachN]:
3281                    measureResult[eachN].insert(0, eachThing)
3282                inNextThing[eachN] = []
3283                # if we got a left-side barline from the previous measure, use it
3284                if nextMeasureLeft is not None:
3285                    measureResult[eachN].leftBarline = nextMeasureLeft
3286                # add this Measure to the Part
3287                parsed[eachN].append(measureResult[eachN])
3288            # if we got a barline for the next <measure>
3289            if 'next @left' in measureResult:
3290                nextMeasureLeft = measureResult['next @left']
3291            else:
3292                nextMeasureLeft = None
3293
3294        elif scoreDefTag == eachElem.tag:
3295            localResult = scoreDefFromElement(eachElem, slurBundle)
3296            for allPartObject in localResult['all-part objects']:
3297                if isinstance(allPartObject, meter.TimeSignature):
3298                    activeMeter = allPartObject
3299                for eachN in allPartNs:
3300                    inNextThing[eachN].append(allPartObject)
3301            for eachN in allPartNs:
3302                if eachN in localResult:
3303                    for eachObj in localResult[eachN].values():
3304                        inNextThing[eachN].append(eachObj)
3305
3306        elif staffDefTag == eachElem.tag:
3307            if eachElem.get('n') is not None:
3308                for eachObj in staffDefFromElement(eachElem, slurBundle).values():
3309                    if isinstance(eachObj, meter.TimeSignature):
3310                        activeMeter = eachObj
3311                    inNextThing[eachElem.get('n')].append(eachObj)
3312            else:
3313                # At the moment, to process this here, we need an @n on the <staffDef>. A document
3314                # may have a still-valid <staffDef> if the <staffDef> has an @xml:id with which
3315                # <staff> elements may refer to it.
3316                environLocal.warn(_UNIMPLEMENTED_IMPORT.format('<staffDef>', '@n'))
3317
3318        elif sectionTag == eachElem.tag:
3319            # NOTE: same as scoreFE() (except the name of "inNextThing")
3320            localParsed, activeMeter, nextMeasureLeft, backupMeasureNum = sectionFromElement(
3321                eachElem,
3322                allPartNs,
3323                activeMeter=activeMeter,
3324                nextMeasureLeft=nextMeasureLeft,
3325                backupMeasureNum=backupMeasureNum,
3326                slurBundle=slurBundle)
3327            for eachN, eachList in localParsed.items():
3328                # NOTE: "eachList" is a list of objects that will become a music21 Part.
3329                #
3330                # first: if there were objects from a previous <scoreDef> or <staffDef>, we need to
3331                #        put those into the first Measure object we encounter in this Part
3332                # TODO: this is where the Instruments get added
3333                # TODO: I think "eachList" really means "each list that will become a Part"
3334                if inNextThing[eachN]:
3335                    # we have to put Instrument objects just before the Measure to which they apply
3336                    theInstr = None
3337                    theInstrI = None
3338                    for i, eachInsertion in enumerate(inNextThing[eachN]):
3339                        if isinstance(eachInsertion, instrument.Instrument):
3340                            theInstr = eachInsertion
3341                            theInstrI = i
3342                            break
3343
3344                    # Put the Instrument right in front, then remove it from "inNextThing" so it
3345                    # doesn't show up twice.
3346                    if theInstr:
3347                        eachList.insert(0, theInstr)
3348                        del inNextThing[eachN][theInstrI]
3349
3350                    for eachObj in eachList:
3351                        # NOTE: "eachObj" is one of the things that will be in the Part, which are
3352                        #       probably but not necessarily Measures
3353                        if isinstance(eachObj, stream.Stream):
3354                            # NOTE: ... but now eachObj is virtually guaranteed to be a Measure
3355                            for eachInsertion in inNextThing[eachN]:
3356                                eachObj.insert(0.0, eachInsertion)
3357                            break
3358                    inNextThing[eachN] = []
3359                # Then we can append the objects in this Part to the dict of all parsed objects, but
3360                # NOTE that this is different for <section> and <score>.
3361                if sectionTag == elem.tag:
3362                    # If this is a <section>, which would really be nested <section> elements, we
3363                    # must "flatten" everything so it doesn't cause a disaster when we try to make
3364                    # a Part out of it.
3365                    for eachObj in eachList:
3366                        parsed[eachN].append(eachObj)
3367                elif scoreTag == elem.tag:
3368                    # If this is a <score>, we can just append the result of each <section> to the
3369                    # list that will become the Part.
3370                    parsed[eachN].append(eachList)
3371
3372        elif eachElem.tag not in _IGNORE_UNPROCESSED:
3373            environLocal.printDebug(_UNPROCESSED_SUBELEMENT.format(eachElem.tag, elem.tag))
3374
3375    # TODO: write the <section @label=""> part
3376    # TODO: check if there's anything left in "inNextThing"
3377
3378    return parsed, activeMeter, nextMeasureLeft, backupMeasureNum
3379
3380
3381def sectionFromElement(elem, allPartNs, activeMeter, nextMeasureLeft, backupMeasureNum, slurBundle):
3382    '''
3383    <section> Segment of music data.
3384
3385    In MEI 2013: pg.432 (446 in PDF) (MEI.shared module)
3386
3387    .. note:: The parameters and return values are exactly the same for :func:`sectionFromElement`
3388        and :func:`sectionScoreCore`, so refer to the latter function's documentation for more
3389        information.
3390
3391    **Attributes/Elements Implemented:**
3392
3393    **Attributes Ignored:**
3394
3395    **Attributes/Elements in Testing:**
3396
3397    - @label
3398    - contained <measure>, <scoreDef>, <staffDef>, <section>
3399
3400    **Attributes not Implemented:**
3401
3402    - att.common (@n, @xml:base) (att.id (@xml:id))
3403    - att.declaring (@decls)
3404    - att.facsimile (@facs)
3405    - att.typed (@type, @subtype)
3406    - att.pointing (@xlink:actuate, @xlink:role, @xlink:show, @target, @targettype, @xlink:title)
3407    - att.section.vis (@restart)
3408    - att.section.anl (att.common.anl (@copyof, @corresp, @next, @prev, @sameas, @synch)
3409                                      (att.alignment (@when)))
3410
3411    **Contained Elements not Implemented:**
3412
3413    - MEI.critapp: app
3414    - MEI.edittrans: add choice corr damage del gap handShift orig reg
3415                     restore sic subst supplied unclear
3416    - MEI.shared: annot ending expansion pb sb section staff
3417    - MEI.text: div
3418    - MEI.usersymbols: anchoredText curve line symbol
3419    '''
3420    environLocal.printDebug('*** processing a <section>')
3421    return sectionScoreCore(elem,
3422                            allPartNs,
3423                            slurBundle,
3424                            activeMeter=activeMeter,
3425                            nextMeasureLeft=nextMeasureLeft,
3426                            backupMeasureNum=backupMeasureNum)
3427
3428
3429def scoreFromElement(elem, slurBundle):
3430    '''
3431    <score> Full score view of the musical content.
3432
3433    In MEI 2013: pg.430 (444 in PDF) (MEI.shared module)
3434
3435    :param elem: The <score> element to process.
3436    :type elem: :class:`~xml.etree.ElementTree.Element`
3437    :param slurBundle: This :class:`SpannerBundle` holds the :class:`~music21.spanner.Slur` objects
3438        created during pre-processing. The slurs are attached to their respective :class:`Note` and
3439        :class:`Chord` objects as they are processed.
3440    :type slurBundle: :class:`music21.spanner.SpannerBundle`
3441    :returns: A completed :class:`~music21.stream.Score` object.
3442
3443    **Attributes/Elements Implemented:**
3444
3445    **Attributes Ignored:**
3446
3447    **Attributes/Elements in Testing:**
3448
3449    - contained <section>, <scoreDef>, and <staffDef>
3450
3451    **Attributes not Implemented:**
3452
3453    - att.common (@label, @n, @xml:base) (att.id (@xml:id))
3454    - att.declaring (@decls)
3455    - att.typed (@type, @subtype)
3456    - att.score.anl (att.common.anl (@copyof, @corresp, @next, @prev, @sameas, @synch)
3457                                    (att.alignment (@when)))
3458
3459    **Contained Elements not Implemented:**
3460
3461    - MEI.critapp: app
3462    - MEI.edittrans: add choice corr damage del gap handShift orig
3463                     reg restore sic subst supplied unclear
3464    - MEI.shared: annot ending pb sb
3465    - MEI.text: div
3466    - MEI.usersymbols: anchoredText curve line symbol
3467    '''
3468
3469    environLocal.printDebug('*** processing a <score>')
3470    # That's an outright lie. We're also processing <scoreDef>, <staffDef>, and other elements!
3471
3472    # Get a tuple of all the @n attributes for the <staff> tags in this score. Each <staff> tag
3473    # corresponds to what will be a music21 Part.
3474    allPartNs = allPartsPresent(elem)
3475
3476    # This is the actual processing.
3477    parsed = sectionScoreCore(elem, allPartNs, slurBundle=slurBundle)[0]
3478
3479    # Convert the dict to a Score
3480    # We must iterate here over "allPartNs," which preserves the part-order found in the MEI
3481    # document. Iterating the keys in "parsed" would not preserve the order.
3482    environLocal.printDebug('*** making the Score')
3483    theScore = [stream.Part() for _ in range(len(allPartNs))]
3484    for i, eachN in enumerate(allPartNs):
3485        # set "atSoundingPitch" so transposition works
3486        theScore[i].atSoundingPitch = False
3487        for eachObj in parsed[eachN]:
3488            theScore[i].append(eachObj)
3489    theScore = stream.Score(theScore)
3490
3491    # put slurs in the Score
3492    theScore.append(list(slurBundle))
3493    # TODO: when all the Slur objects are at the end, they'll only be outputted properly if the
3494    #       whole Score is outputted. show()-ing one Part or Measure won't display the slurs.
3495
3496    return theScore
3497
3498
3499# -----------------------------------------------------------------------------
3500_DOC_ORDER = [
3501    accidFromElement,
3502    articFromElement,
3503    beamFromElement,
3504    chordFromElement,
3505    clefFromElement,
3506    dotFromElement,
3507    instrDefFromElement,
3508    layerFromElement,
3509    measureFromElement,
3510    noteFromElement,
3511    spaceFromElement,
3512    mSpaceFromElement,
3513    restFromElement,
3514    mRestFromElement,
3515    scoreFromElement,
3516    sectionFromElement,
3517    scoreDefFromElement,
3518    staffFromElement,
3519    staffDefFromElement,
3520    staffGrpFromElement,
3521    tupletFromElement,
3522]
3523
3524if __name__ == '__main__':
3525    import music21
3526    music21.mainTest()
3527