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