1# -*- coding: utf-8 -*- 2# ------------------------------------------------------------------------------ 3# Name: abc/__init__.py 4# Purpose: parses ABC Notation 5# 6# Authors: Christopher Ariza 7# Dylan J. Nagler 8# Michael Scott Cuthbert 9# 10# Copyright: Copyright © 2010, 2013 Michael Scott Cuthbert and the music21 Project 11# License: BSD, see license.txt 12# ------------------------------------------------------------------------------ 13''' 14ABC is a music format that, while being able to encode all sorts of scores, is especially 15strong at representing monophonic music, and folk music in particular. 16 17Modules in the `music21.abcFormat` package deal with importing ABC into music21. Most people 18working with ABC data won't need to use this package. To convert ABC from a file or URL 19to a :class:`~music21.stream.Stream` use the :func:`~music21.converter.parse` function of 20the `converter` module: 21 22>>> #_DOCS_SHOW from music21 import * 23>>> #_DOCS_SHOW abcScore = converter.parse('/users/ariza/myScore.abc') 24 25For users who will be editing ABC extensively or need a way to have music21 output ABC 26(which it doesn't do natively), we suggest using the open source EasyABC package: 27https://www.nilsliberg.se/ksp/easyabc/ . You can set it up as a MusicXML reader through: 28 29>>> #_DOCS_SHOW us = environment.UserSettings() 30>>> #_DOCS_SHOW us['musicxmlPath'] = '/Applications/EasyABC.app' 31 32or wherever you have downloaded EasyABC to 33(PC users might need: 'c:/program files (x86)/easyabc/easyabc.exe') 34(Thanks to Norman Schmidt for the heads up) 35 36There is a two-step process in converting ABC files to Music21 Streams. First this module 37reads in the text-based .abc file and converts all the information into ABCToken objects. Then 38the function :func:`music21.abcFormat.translate.abcToStreamScore` of 39the :ref:`moduleAbcFormatTranslate` module 40translates those Tokens into music21 objects. 41''' 42__all__ = [ 43 'translate', 44 'testFiles', 45 'ABCTokenException', 'ABCHandlerException', 'ABCFileException', 46 'ABCToken', 47 'ABCMetadata', 'ABCBar', 'ABCTuplet', 'ABCTie', 48 'ABCSlurStart', 'ABCParenStop', 'ABCCrescStart', 'ABCDimStart', 49 'ABCStaccato', 'ABCUpbow', 'ABCDownbow', 'ABCAccent', 'ABCStraccent', 50 'ABCTenuto', 'ABCGraceStart', 'ABCGraceStop', 'ABCBrokenRhythmMarker', 51 'ABCNote', 'ABCChord', 52 'ABCHandler', 'ABCHandlerBar', 53 'mergeLeadingMetaData', 54 'ABCFile', 55] 56 57import io 58import re 59import unittest 60from typing import Union, Optional, List, Tuple, Any 61 62from music21 import common 63from music21 import environment 64from music21 import exceptions21 65from music21 import prebase 66 67from music21.abcFormat import translate 68 69environLocal = environment.Environment('abcFormat') 70 71# for implementation 72# see http://abcnotation.com/abc2mtex/abc.txt 73 74# store symbol and m21 naming/class eq 75ABC_BARS = [ 76 (':|1', 'light-heavy-repeat-end-first'), 77 (':|2', 'light-heavy-repeat-end-second'), 78 ('|]', 'light-heavy'), 79 ('||', 'light-light'), 80 ('[|', 'heavy-light'), 81 ('[1', 'regular-first'), # preferred format 82 ('[2', 'regular-second'), 83 ('|1', 'regular-first'), # gets converted 84 ('|2', 'regular-second'), 85 (':|', 'light-heavy-repeat-end'), 86 ('|:', 'heavy-light-repeat-start'), 87 ('::', 'heavy-heavy-repeat-bidirectional'), 88 # for comparison, single chars must go last 89 ('|', 'regular'), 90 (':', 'dotted'), 91] 92 93# store a mapping of ABC representation to pitch values 94_pitchTranslationCache = {} 95 96 97# ------------------------------------------------------------------------------ 98# note inclusion of w: for lyrics 99reMetadataTag = re.compile('[A-Zw]:') 100rePitchName = re.compile('[a-gA-Gz]') 101reChordSymbol = re.compile('"[^"]*"') # non greedy 102reChord = re.compile('[.*?]') # non greedy 103reAbcVersion = re.compile(r'^%abc-((\d+)\.(\d+)\.?(\d+)?)') 104reDirective = re.compile(r'^%%([a-z\-]+)\s+([^\s]+)(.*)') 105 106 107# ------------------------------------------------------------------------------ 108class ABCTokenException(exceptions21.Music21Exception): 109 pass 110 111 112class ABCHandlerException(exceptions21.Music21Exception): 113 pass 114 115 116class ABCFileException(exceptions21.Music21Exception): 117 pass 118# 119 120 121# ------------------------------------------------------------------------------ 122class ABCToken(prebase.ProtoM21Object): 123 ''' 124 ABC processing works with a multi-pass procedure. The first pass 125 breaks the data stream into a list of ABCToken objects. ABCToken 126 objects are specialized in subclasses. 127 128 The multi-pass procedure is conducted by an ABCHandler object. 129 The ABCHandler.tokenize() method breaks the data stream into 130 ABCToken objects. The :meth:`~music21.abcFormat.ABCHandler.tokenProcess` method first 131 calls the :meth:`~music21.abcFormat.ABCToken.preParse` method on each token, 132 then does contextual 133 adjustments to all tokens, then calls :meth:`~music21.abcFormat.ABCToken.parse` on all tokens. 134 135 The source ABC string itself is stored in self.src 136 ''' 137 def __init__(self, src=''): 138 self.src: str = src # store source character sequence 139 140 def _reprInternal(self): 141 return repr(self.src) 142 143 @staticmethod 144 def stripComment(strSrc): 145 ''' 146 removes ABC-style comments from a string: 147 148 >>> ao = abcFormat.ABCToken() 149 >>> ao.stripComment('asdf') 150 'asdf' 151 >>> ao.stripComment('asdf%234') 152 'asdf' 153 >>> ao.stripComment('asdf % 234') 154 'asdf ' 155 >>> ao.stripComment('[ceg]% this chord appears 50% more often than other chords do') 156 '[ceg]' 157 158 This is a static method, so it can also be called on the class itself: 159 160 >>> abcFormat.ABCToken.stripComment('b1 % a b-flat actually') 161 'b1 ' 162 163 Changed: v6.2 -- made a staticmethod 164 ''' 165 if '%' in strSrc: 166 return strSrc.split('%')[0] 167 return strSrc 168 169 def preParse(self): 170 ''' 171 Dummy method that is called before contextual adjustments. 172 Designed to be subclassed or overridden. 173 ''' 174 pass 175 176 def parse(self): 177 ''' 178 Dummy method that reads self.src and loads attributes. 179 It is called after contextual adjustments. 180 181 It is designed to be subclassed or overridden. 182 ''' 183 pass 184 185 186class ABCMetadata(ABCToken): 187 ''' 188 Defines a token of metadata in ABC. 189 190 >>> md = abcFormat.ABCMetadata('I:linebreak') 191 >>> md.src 192 'I:linebreak' 193 194 Has two attributes, `tag` and `data` which are strings or None. 195 Initially both are set to None: 196 197 >>> print(md.tag) 198 None 199 200 After calling `preParse()`, these are separated: 201 202 >>> md.preParse() 203 >>> md.tag 204 'I' 205 >>> md.data 206 'linebreak' 207 ''' 208 # given a logical unit, create an object 209 # may be a chord, notes, metadata, bars 210 def __init__(self, src=''): 211 super().__init__(src) 212 self.tag: Optional[str] = None 213 self.data: Optional[str] = None 214 215 def preParse(self) -> None: 216 ''' 217 Called before contextual adjustments and needs 218 to have access to data. Divides a token into 219 .tag (a single capital letter or w) and .data representations. 220 221 >>> x = abcFormat.ABCMetadata('T:tagData') 222 >>> x.preParse() 223 >>> x.tag 224 'T' 225 >>> x.data 226 'tagData' 227 ''' 228 div = reMetadataTag.match(self.src).end() 229 strSrc = self.stripComment(self.src) # remove any comments 230 self.tag = strSrc[:div - 1] # do not get colon, : 231 self.data = strSrc[div:].strip() # remove leading/trailing 232 233 def parse(self): 234 pass 235 236 def isDefaultNoteLength(self) -> bool: 237 ''' 238 Returns True if the tag is "L", False otherwise. 239 ''' 240 if self.tag == 'L': 241 return True 242 return False 243 244 def isReferenceNumber(self) -> bool: 245 ''' 246 Returns True if the tag is "X", False otherwise. 247 248 >>> x = abcFormat.ABCMetadata('X:5') 249 >>> x.preParse() 250 >>> x.tag 251 'X' 252 >>> x.isReferenceNumber() 253 True 254 ''' 255 if self.tag == 'X': 256 return True 257 return False 258 259 def isMeter(self) -> bool: 260 ''' 261 Returns True if the tag is "M" for meter, False otherwise. 262 ''' 263 if self.tag == 'M': 264 return True 265 return False 266 267 def isTitle(self) -> bool: 268 ''' 269 Returns True if the tag is "T" for title, False otherwise. 270 ''' 271 if self.tag == 'T': 272 return True 273 return False 274 275 def isComposer(self) -> bool: 276 ''' 277 Returns True if the tag is "C" for composer, False otherwise. 278 ''' 279 if self.tag == 'C': 280 return True 281 return False 282 283 def isOrigin(self) -> bool: 284 ''' 285 Returns True if the tag is "O" for origin, False otherwise. 286 This value is set in the Metadata `localOfComposition` of field. 287 ''' 288 if self.tag == 'O': 289 return True 290 return False 291 292 def isVoice(self) -> bool: 293 ''' 294 Returns True if the tag is "V", False otherwise. 295 ''' 296 if self.tag == 'V': 297 return True 298 return False 299 300 def isKey(self) -> bool: 301 ''' 302 Returns True if the tag is "K", False otherwise. 303 Note that in some cases a Key will encode clef information. 304 305 (example from corpus: josquin/laDeplorationDeLaMorteDeJohannesOckeghem.abc) 306 ''' 307 if self.tag == 'K': 308 return True 309 return False 310 311 def isTempo(self) -> bool: 312 ''' 313 Returns True if the tag is "Q" for tempo, False otherwise. 314 ''' 315 if self.tag == 'Q': 316 return True 317 return False 318 319 def getTimeSignatureParameters(self): 320 ''' 321 If there is a time signature representation available, 322 get a numerator, denominator and an abbreviation symbol. 323 To get a music21 :class:`~music21.meter.TimeSignature` object, use 324 the :meth:`~music21.abcFormat.ABCMetadata.getTimeSignatureObject` method. 325 326 >>> am = abcFormat.ABCMetadata('M:2/2') 327 >>> am.preParse() 328 >>> am.isMeter() 329 True 330 >>> am.getTimeSignatureParameters() 331 (2, 2, 'normal') 332 333 >>> am = abcFormat.ABCMetadata('M:C|') 334 >>> am.preParse() 335 >>> am.getTimeSignatureParameters() 336 (2, 2, 'cut') 337 338 >>> am = abcFormat.ABCMetadata('M: none') 339 >>> am.preParse() 340 >>> am.getTimeSignatureParameters() is None 341 True 342 343 >>> am = abcFormat.ABCMetadata('M: FREI4/4') 344 >>> am.preParse() 345 >>> am.getTimeSignatureParameters() 346 (4, 4, 'normal') 347 ''' 348 if not self.isMeter(): 349 raise ABCTokenException('no time signature associated with this metadata') 350 351 if self.data.lower() == 'none': 352 return None 353 elif self.data == 'C': 354 n, d = 4, 4 355 symbol = 'common' # m21 compat 356 elif self.data == 'C|': 357 n, d = 2, 2 358 symbol = 'cut' # m21 compat 359 else: 360 n, d = self.data.split('/') 361 # using get number from string to handle odd cases such as 362 # FREI4/4 363 n = int(common.getNumFromStr(n.strip())[0]) 364 d = int(common.getNumFromStr(d.strip())[0]) 365 symbol = 'normal' # m21 compat 366 return n, d, symbol 367 368 def getTimeSignatureObject(self): 369 ''' 370 Return a music21 :class:`~music21.meter.TimeSignature` 371 object for this metadata tag, if isMeter is True, otherwise raise exception. 372 373 >>> am = abcFormat.ABCMetadata('M:2/2') 374 >>> am.preParse() 375 >>> ts = am.getTimeSignatureObject() 376 >>> ts 377 <music21.meter.TimeSignature 2/2> 378 379 >>> am = abcFormat.ABCMetadata('Q:40') 380 >>> am.getTimeSignatureObject() 381 Traceback (most recent call last): 382 music21.abcFormat.ABCTokenException: no time signature associated with 383 this non-metrical metadata. 384 ''' 385 if not self.isMeter(): 386 raise ABCTokenException( 387 'no time signature associated with this non-metrical metadata.') 388 from music21 import meter 389 parameters = self.getTimeSignatureParameters() 390 if parameters is None: 391 return None 392 else: 393 numerator, denominator, unused_symbol = parameters 394 return meter.TimeSignature(f'{numerator}/{denominator}') 395 396 def getKeySignatureParameters(self): 397 # noinspection SpellCheckingInspection 398 ''' 399 Extract key signature parameters, include indications for mode, 400 and translate sharps count compatible with m21, 401 returning the number of sharps and the mode. 402 403 >>> from music21 import abcFormat 404 405 >>> am = abcFormat.ABCMetadata('K:Eb Lydian') 406 >>> am.preParse() 407 >>> am.getKeySignatureParameters() 408 (-2, 'lydian') 409 410 >>> am = abcFormat.ABCMetadata('K:APhry') 411 >>> am.preParse() 412 >>> am.getKeySignatureParameters() 413 (-1, 'phrygian') 414 415 >>> am = abcFormat.ABCMetadata('K:G Mixolydian') 416 >>> am.preParse() 417 >>> am.getKeySignatureParameters() 418 (0, 'mixolydian') 419 420 >>> am = abcFormat.ABCMetadata('K: Edor') 421 >>> am.preParse() 422 >>> am.getKeySignatureParameters() 423 (2, 'dorian') 424 425 >>> am = abcFormat.ABCMetadata('K: F') 426 >>> am.preParse() 427 >>> am.getKeySignatureParameters() 428 (-1, 'major') 429 430 >>> am = abcFormat.ABCMetadata('K:G') 431 >>> am.preParse() 432 >>> am.getKeySignatureParameters() 433 (1, 'major') 434 435 >>> am = abcFormat.ABCMetadata('K:Gm') 436 >>> am.preParse() 437 >>> am.getKeySignatureParameters() 438 (-2, 'minor') 439 440 >>> am = abcFormat.ABCMetadata('K:Hp') 441 >>> am.preParse() 442 >>> am.getKeySignatureParameters() 443 (2, None) 444 445 >>> am = abcFormat.ABCMetadata('K:G ionian') 446 >>> am.preParse() 447 >>> am.getKeySignatureParameters() 448 (1, 'ionian') 449 450 >>> am = abcFormat.ABCMetadata('K:G aeol') 451 >>> am.preParse() 452 >>> am.getKeySignatureParameters() 453 (-2, 'aeolian') 454 455 ''' 456 # placing this import in method for now; key.py may import this module 457 from music21 import key 458 459 if not self.isKey(): 460 raise ABCTokenException('no key signature associated with this metadata.') 461 462 # abc uses b for flat in key spec only 463 keyNameMatch = ['c', 'g', 'd', 'a', 'e', 'b', 'f#', 'g#', 'a#', 464 'f', 'bb', 'eb', 'd#', 'ab', 'e#', 'db', 'c#', 'gb', 'cb', 465 # HP or Hp are used for highland pipes 466 'hp'] 467 468 # if no match, provide defaults, 469 # this is probably an error or badly formatted 470 standardKeyStr = 'C' 471 stringRemain = '' 472 # first, get standard key indication 473 for target in sorted(keyNameMatch, key=len, reverse=True): 474 if target == self.data[:len(target)].lower(): 475 # keep case 476 standardKeyStr = self.data[:len(target)] 477 stringRemain = self.data[len(target):] 478 break 479 480 if len(standardKeyStr) > 1 and standardKeyStr[1] == 'b': 481 standardKeyStr = standardKeyStr[0] + '-' 482 483 mode = None 484 stringRemain = stringRemain.strip() 485 if stringRemain == '': 486 # Assume mode is major by default 487 mode = 'major' 488 else: 489 # only first three characters are parsed 490 modeCandidate = stringRemain.lower() 491 for match, modeStr in ( 492 ('dor', 'dorian'), 493 ('phr', 'phrygian'), 494 ('lyd', 'lydian'), 495 ('mix', 'mixolydian'), 496 ('maj', 'major'), 497 ('ion', 'ionian'), 498 ('aeo', 'aeolian'), 499 ('m', 'minor'), 500 ): 501 if modeCandidate.startswith(match): 502 mode = modeStr 503 break 504 505 # Special case for highland pipes 506 # replace a flat symbol if found; only the second char 507 if standardKeyStr == 'HP': 508 standardKeyStr = 'C' # no sharp or flats 509 mode = None 510 elif standardKeyStr == 'Hp': 511 standardKeyStr = 'D' # use F#, C#, Gn 512 mode = None 513 514 # not yet implemented: checking for additional chromatic alternations 515 # e.g.: K:D =c would write the key signature as two sharps 516 # (key of D) but then mark every c as natural 517 return key.pitchToSharps(standardKeyStr, mode), mode 518 519 def getKeySignatureObject(self): 520 # noinspection SpellCheckingInspection,PyShadowingNames 521 ''' 522 Return a music21 :class:`~music21.key.KeySignature` or :class:`~music21.key.Key` 523 object for this metadata tag. 524 525 526 >>> am = abcFormat.ABCMetadata('K:G') 527 >>> am.preParse() 528 >>> ks = am.getKeySignatureObject() 529 >>> ks 530 <music21.key.Key of G major> 531 532 >>> am = abcFormat.ABCMetadata('K:Gmin') 533 >>> am.preParse() 534 >>> ks = am.getKeySignatureObject() 535 >>> ks 536 <music21.key.Key of g minor> 537 >>> ks.sharps 538 -2 539 540 Note that capitalization does not matter 541 (http://abcnotation.com/wiki/abc:standard:v2.1#kkey) 542 so this should still be minor. 543 544 >>> am = abcFormat.ABCMetadata('K:GM') 545 >>> am.preParse() 546 >>> ks = am.getKeySignatureObject() 547 >>> ks 548 <music21.key.Key of g minor> 549 ''' 550 if not self.isKey(): 551 raise ABCTokenException('no key signature associated with this metadata') 552 from music21 import key 553 # return values of getKeySignatureParameters are sharps, mode 554 # need to unpack list w/ * 555 sharps, mode = self.getKeySignatureParameters() 556 ks = key.KeySignature(sharps) 557 if mode in (None, ''): 558 return ks 559 else: 560 return ks.asKey(mode) 561 562 def getClefObject(self) -> Tuple[Optional['music21.clef.Clef'], Optional[int]]: 563 ''' 564 Extract any clef parameters stored in the key metadata token. 565 Assume that a clef definition suggests a transposition. 566 Return both the Clef and the transposition. 567 568 Returns a two-element tuple of clefObj and transposition in semitones 569 570 >>> am = abcFormat.ABCMetadata('K:Eb Lydian bass') 571 >>> am.preParse() 572 >>> am.getClefObject() 573 (<music21.clef.BassClef>, -24) 574 ''' 575 if not self.isKey(): 576 raise ABCTokenException( 577 'no key signature associated with this metadata; needed for getting Clef Object') 578 579 # placing this import in method for now; key.py may import this module 580 clefObj = None 581 t = None 582 583 from music21 import clef 584 if '-8va' in self.data.lower(): 585 clefObj = clef.Treble8vbClef() 586 t = -12 587 elif 'bass' in self.data.lower(): 588 clefObj = clef.BassClef() 589 t = -24 590 591 # if not defined, returns None, None 592 return clefObj, t 593 594 def getMetronomeMarkObject(self) -> Optional['music21.tempo.MetronomeMark']: 595 ''' 596 Extract any tempo parameters stored in a tempo metadata token. 597 598 >>> am = abcFormat.ABCMetadata('Q: "Allegro" 1/4=120') 599 >>> am.preParse() 600 >>> am.getMetronomeMarkObject() 601 <music21.tempo.MetronomeMark Allegro Quarter=120.0> 602 603 >>> am = abcFormat.ABCMetadata('Q: 3/8=50 "Slowly"') 604 >>> am.preParse() 605 >>> am.getMetronomeMarkObject() 606 <music21.tempo.MetronomeMark Slowly Dotted Quarter=50.0> 607 608 >>> am = abcFormat.ABCMetadata('Q:1/2=120') 609 >>> am.preParse() 610 >>> am.getMetronomeMarkObject() 611 <music21.tempo.MetronomeMark animato Half=120.0> 612 613 >>> am = abcFormat.ABCMetadata('Q:1/4 3/8 1/4 3/8=40') 614 >>> am.preParse() 615 >>> am.getMetronomeMarkObject() 616 <music21.tempo.MetronomeMark grave Whole tied to Quarter (5 total QL)=40.0> 617 618 >>> am = abcFormat.ABCMetadata('Q:90') 619 >>> am.preParse() 620 >>> am.getMetronomeMarkObject() 621 <music21.tempo.MetronomeMark maestoso Quarter=90.0> 622 623 ''' 624 if not self.isTempo(): 625 raise ABCTokenException('no tempo associated with this metadata') 626 mmObj = None 627 from music21 import tempo 628 # see if there is a text expression in quotes 629 tempoStr = None 630 if '"' in self.data: 631 tempoStr = [] 632 nonText = [] 633 isOpen = False 634 for char in self.data: 635 if char == '"' and not isOpen: 636 isOpen = True 637 continue 638 if char == '"' and isOpen: 639 isOpen = False 640 continue 641 if isOpen: 642 tempoStr.append(char) 643 else: # gather all else 644 nonText.append(char) 645 tempoStr = ''.join(tempoStr).strip() 646 nonText = ''.join(nonText).strip() 647 else: 648 nonText = self.data.strip() 649 650 # get a symbolic and numerical value if available 651 number = None 652 referent = None 653 if nonText: 654 if '=' in nonText: 655 durs, number = nonText.split('=') 656 number = float(number) 657 # there may be more than one dur divided by a space 658 referent = 0.0 # in quarter lengths 659 for dur in durs.split(' '): 660 if dur.count('/') > 0: 661 n, d = dur.split('/') 662 else: # this is an error case 663 environLocal.printDebug(['incorrectly encoded / unparsable duration:', dur]) 664 n, d = '1', '1' 665 # n and d might be strings... 666 referent += (float(n) / float(d)) * 4 667 else: # assume we just have a quarter definition, e.g., Q:90 668 number = float(nonText) 669 670 # print(nonText, tempoStr) 671 if tempoStr is not None or number is not None: 672 mmObj = tempo.MetronomeMark(text=tempoStr, number=number, 673 referent=referent) 674 # returns None if not defined 675 return mmObj 676 677 def getDefaultQuarterLength(self) -> float: 678 r''' 679 If there is a quarter length representation available, return it as a floating point value 680 681 >>> am = abcFormat.ABCMetadata('L:1/2') 682 >>> am.preParse() 683 >>> am.getDefaultQuarterLength() 684 2.0 685 686 >>> am = abcFormat.ABCMetadata('L:1/8') 687 >>> am.preParse() 688 >>> am.getDefaultQuarterLength() 689 0.5 690 691 >>> am = abcFormat.ABCMetadata('M:C|') 692 >>> am.preParse() 693 >>> am.getDefaultQuarterLength() 694 0.5 695 696 697 If taking from meter, find the "fraction" and if < 0.75 use sixteenth notes. 698 If >= 0.75 use eighth notes. 699 700 >>> am = abcFormat.ABCMetadata('M:2/4') 701 >>> am.preParse() 702 >>> am.getDefaultQuarterLength() 703 0.25 704 705 >>> am = abcFormat.ABCMetadata('M:3/4') 706 >>> am.preParse() 707 >>> am.getDefaultQuarterLength() 708 0.5 709 710 711 >>> am = abcFormat.ABCMetadata('M:6/8') 712 >>> am.preParse() 713 >>> am.getDefaultQuarterLength() 714 0.5 715 716 717 Meter is only used for default length if there is no L: 718 719 >>> x = 'L:1/4\nM:3/4\n\nf' 720 >>> sc = converter.parse(x, format='abc') 721 >>> sc.recurse().notes.first().duration.type 722 'quarter' 723 ''' 724 # environLocal.printDebug(['getDefaultQuarterLength', self.data]) 725 if self.isDefaultNoteLength() and '/' in self.data: 726 # should be in L:1/4 form 727 n, d = self.data.split('/') 728 n = int(n.strip()) 729 # the notation L: 1/G is found in some essen files 730 # this is extremely uncommon and might be an error 731 if d == 'G': 732 d = 4 # assume a default 733 else: 734 d = int(d.strip()) 735 # 1/4 is 1, 1/8 is 0.5 736 return n * 4 / d 737 738 elif self.isMeter(): 739 # if meter auto-set a default not length 740 parameters = self.getTimeSignatureParameters() 741 if parameters is None: 742 return 0.5 # TODO: assume default, need to configure 743 n, d, unused_symbol = parameters 744 if n / d < 0.75: 745 return 0.25 # less than 0.75 the default is a sixteenth note 746 else: 747 return 0.5 # otherwise it is an eighth note 748 else: # pragma: no cover 749 raise ABCTokenException( 750 f'no quarter length associated with this metadata: {self.data}') 751 752 753class ABCBar(ABCToken): 754 # given a logical unit, create an object 755 # may be a chord, notes, metadata, bars 756 def __init__(self, src): 757 super().__init__(src) 758 self.barType = None # repeat or barline 759 self.barStyle = None # regular, heavy-light, etc 760 self.repeatForm = None # end, start, bidrectional, first, second 761 762 def parse(self): 763 ''' 764 Assign the bar-type based on the source string. 765 766 >>> ab = abcFormat.ABCBar('|') 767 >>> ab.parse() 768 >>> ab 769 <music21.abcFormat.ABCBar '|'> 770 771 >>> ab.barType 772 'barline' 773 >>> ab.barStyle 774 'regular' 775 776 >>> ab = abcFormat.ABCBar('||') 777 >>> ab.parse() 778 >>> ab.barType 779 'barline' 780 >>> ab.barStyle 781 'light-light' 782 783 >>> ab = abcFormat.ABCBar('|:') 784 >>> ab.parse() 785 >>> ab.barType 786 'repeat' 787 >>> ab.barStyle 788 'heavy-light' 789 >>> ab.repeatForm 790 'start' 791 ''' 792 for abcStr, barTypeString in ABC_BARS: 793 if abcStr == self.src.strip(): 794 # this gets lists of elements like 795 # light-heavy-repeat-end 796 barTypeComponents = barTypeString.split('-') 797 # this is a list of attributes 798 if 'repeat' in barTypeComponents: 799 self.barType = 'repeat' 800 elif ('first' in barTypeComponents 801 or 'second' in barTypeComponents): 802 self.barType = 'barline' 803 # environLocal.printDebug(['got repeat 1/2:', self.src]) 804 else: 805 self.barType = 'barline' 806 807 # case of regular, dotted 808 if len(barTypeComponents) == 1: 809 self.barStyle = barTypeComponents[0] 810 811 # case of light-heavy, light-light, etc 812 elif len(barTypeComponents) >= 2: 813 # must get out cases of the start-tags for repeat boundaries 814 # not yet handling 815 if 'first' in barTypeComponents: 816 self.barStyle = 'regular' 817 self.repeatForm = 'first' # not a repeat 818 elif 'second' in barTypeComponents: 819 self.barStyle = 'regular' 820 self.repeatForm = 'second' # not a repeat 821 else: 822 self.barStyle = barTypeComponents[0] + '-' + barTypeComponents[1] 823 # repeat form is either start/end for normal repeats 824 # get extra repeat information; start, end, first, second 825 if len(barTypeComponents) > 2: 826 self.repeatForm = barTypeComponents[3] 827 828 def isRepeat(self): 829 if self.barType == 'repeat': 830 return True 831 else: 832 return False 833 834 def isRegular(self) -> bool: 835 ''' 836 Return True if this is a regular, single, light bar line. 837 838 >>> ab = abcFormat.ABCBar('|') 839 >>> ab.parse() 840 >>> ab.isRegular() 841 True 842 ''' 843 if self.barType != 'repeat' and self.barStyle == 'regular': 844 return True 845 else: 846 return False 847 848 def isRepeatBracket(self) -> Union[int, bool]: 849 ''' 850 Return a number if this defines a repeat bracket for an alternate ending 851 otherwise returns False. 852 853 >>> ab = abcFormat.ABCBar('[2') 854 >>> ab.parse() 855 >>> ab.isRepeat() 856 False 857 >>> ab.isRepeatBracket() 858 2 859 ''' 860 if self.repeatForm == 'first': 861 return 1 # we need a number 862 elif self.repeatForm == 'second': 863 return 2 864 else: 865 return False 866 867 def getBarObject(self) -> Optional['music21.bar.Barline']: 868 ''' 869 Return a music21 bar object 870 871 >>> ab = abcFormat.ABCBar('|:') 872 >>> ab.parse() 873 >>> barObject = ab.getBarObject() 874 >>> barObject 875 <music21.bar.Repeat direction=start> 876 ''' 877 from music21 import bar 878 if self.isRepeat(): 879 if self.repeatForm in ('end', 'start'): 880 m21bar = bar.Repeat(direction=self.repeatForm) 881 # bidirectional repeat tokens should already have been replaced 882 # by end and start 883 else: # pragma: no cover 884 environLocal.printDebug( 885 [f'found an unsupported repeatForm in ABC: {self.repeatForm}'] 886 ) 887 m21bar = None 888 elif self.barStyle == 'regular': 889 m21bar = None # do not need an object for regular 890 elif self.repeatForm in ('first', 'second'): 891 # do nothing, as this is handled in translation 892 m21bar = None 893 else: 894 m21bar = bar.Barline(self.barStyle) 895 return m21bar 896 897 898class ABCTuplet(ABCToken): 899 ''' 900 ABCTuplet tokens always precede the notes they describe. 901 902 In ABCHandler.tokenProcess(), rhythms are adjusted. 903 ''' 904 def __init__(self, src): 905 super().__init__(src) 906 907 # self.qlRemain = None # how many ql are left of this tuplets activity 908 # how many notes are affected by this; this assumes equal duration 909 self.noteCount = None 910 911 # actual is tuplet represented value; 3 in 3:2 912 self.numberNotesActual = None 913 # self.durationActual = None 914 915 # normal is underlying duration representation; 2 in 3:2 916 self.numberNotesNormal = None 917 # self.durationNormal = None 918 919 # store an m21 tuplet object 920 self.tupletObj = None 921 922 def updateRatio(self, keySignatureObj=None): 923 # noinspection PyShadowingNames 924 ''' 925 Cannot be called until local meter context 926 is established. 927 928 >>> at = abcFormat.ABCTuplet('(3') 929 >>> at.updateRatio() 930 >>> at.numberNotesActual, at.numberNotesNormal 931 (3, 2) 932 933 Generally a 5:n tuplet is 5 in the place of 2. 934 935 >>> at = abcFormat.ABCTuplet('(5') 936 >>> at.updateRatio() 937 >>> at.numberNotesActual, at.numberNotesNormal 938 (5, 2) 939 940 Unless it's in a meter.TimeSignature compound (triple) context: 941 942 >>> at = abcFormat.ABCTuplet('(5') 943 >>> at.updateRatio(meter.TimeSignature('6/8')) 944 >>> at.numberNotesActual, at.numberNotesNormal 945 (5, 3) 946 947 Six is 6:2, not 6:4! 948 949 >>> at = abcFormat.ABCTuplet('(6') 950 >>> at.updateRatio() 951 >>> at.numberNotesActual, at.numberNotesNormal 952 (6, 2) 953 954 >>> at = abcFormat.ABCTuplet('(6:4') 955 >>> at.updateRatio() 956 >>> at.numberNotesActual, at.numberNotesNormal 957 (6, 4) 958 959 >>> at = abcFormat.ABCTuplet('(6::6') 960 >>> at.updateRatio() 961 >>> at.numberNotesActual, at.numberNotesNormal 962 (6, 2) 963 964 2 is 2 in 3... 965 966 >>> at = abcFormat.ABCTuplet('(2') 967 >>> at.updateRatio() 968 >>> at.numberNotesActual, at.numberNotesNormal 969 (2, 3) 970 971 972 Some other types: 973 974 >>> for n in 1, 2, 3, 4, 5, 6, 7, 8, 9: 975 ... at = abcFormat.ABCTuplet(f'({n}') 976 ... at.updateRatio() 977 ... print(at.numberNotesActual, at.numberNotesNormal) 978 1 1 979 2 3 980 3 2 981 4 3 982 5 2 983 6 2 984 7 2 985 8 3 986 9 2 987 988 Tuplets > 9 raise an exception: 989 990 >>> at = abcFormat.ABCTuplet('(10') 991 >>> at.updateRatio() 992 Traceback (most recent call last): 993 music21.abcFormat.ABCTokenException: cannot handle tuplet of form: '(10' 994 ''' 995 if keySignatureObj is None: 996 normalSwitch = 2 # 4/4 997 elif keySignatureObj.beatDivisionCount == 3: # if compound 998 normalSwitch = 3 999 else: 1000 normalSwitch = 2 1001 1002 splitTuplet = self.src.strip().split(':') 1003 1004 tupletNumber = splitTuplet[0] 1005 normalNotes = None 1006 1007 if len(splitTuplet) >= 2 and splitTuplet[1] != '': 1008 normalNotes = int(splitTuplet[1]) 1009 1010 if tupletNumber == '(1': # not sure if valid, but found 1011 a, n = 1, 1 1012 elif tupletNumber == '(2': 1013 a, n = 2, 3 # actual, normal 1014 elif tupletNumber == '(3': 1015 a, n = 3, 2 # actual, normal 1016 elif tupletNumber == '(4': 1017 a, n = 4, 3 # actual, normal 1018 elif tupletNumber == '(5': 1019 a, n = 5, normalSwitch # actual, normal 1020 elif tupletNumber == '(6': 1021 a, n = 6, 2 # actual, normal 1022 elif tupletNumber == '(7': 1023 a, n = 7, normalSwitch # actual, normal 1024 elif tupletNumber == '(8': 1025 a, n = 8, 3 # actual, normal 1026 elif tupletNumber == '(9': 1027 a, n = 9, normalSwitch # actual, normal 1028 else: 1029 raise ABCTokenException(f'cannot handle tuplet of form: {tupletNumber!r}') 1030 1031 if normalNotes is None: 1032 normalNotes = n 1033 1034 self.numberNotesActual = a 1035 self.numberNotesNormal = normalNotes 1036 1037 def updateNoteCount(self): 1038 ''' 1039 Update the note count of notes that are 1040 affected by this tuplet. Can be set by p:q:r style tuplets. 1041 Also creates a tuplet object. 1042 1043 >>> at = abcFormat.ABCTuplet('(6') 1044 >>> at.updateRatio() 1045 >>> at.updateNoteCount() 1046 >>> at.noteCount 1047 6 1048 >>> at.tupletObj 1049 <music21.duration.Tuplet 6/2> 1050 1051 >>> at = abcFormat.ABCTuplet('(6:4:12') 1052 >>> at.updateRatio() 1053 >>> at.updateNoteCount() 1054 >>> at.noteCount 1055 12 1056 >>> at.tupletObj 1057 <music21.duration.Tuplet 6/4> 1058 1059 >>> at = abcFormat.ABCTuplet('(6::18') 1060 >>> at.updateRatio() 1061 >>> at.updateNoteCount() 1062 >>> at.noteCount 1063 18 1064 ''' 1065 if self.numberNotesActual is None: 1066 raise ABCTokenException('must set numberNotesActual with updateRatio()') 1067 1068 # nee dto 1069 from music21 import duration 1070 self.tupletObj = duration.Tuplet( 1071 numberNotesActual=self.numberNotesActual, 1072 numberNotesNormal=self.numberNotesNormal) 1073 1074 # copy value; this will be dynamically counted down 1075 splitTuplet = self.src.strip().split(':') 1076 if len(splitTuplet) >= 3 and splitTuplet[2] != '': 1077 self.noteCount = int(splitTuplet[2]) 1078 else: 1079 self.noteCount = self.numberNotesActual 1080 1081 # self.qlRemain = self._tupletObj.totalTupletLength() 1082 1083 1084class ABCTie(ABCToken): 1085 ''' 1086 Handles instances of ties '-' between notes in an ABC score. 1087 Ties are treated as an attribute of the note before the '-'; 1088 the note after is marked as the end of the tie. 1089 ''' 1090 def __init__(self, src): 1091 super().__init__(src) 1092 self.noteObj = None 1093 1094 1095class ABCSlurStart(ABCToken): 1096 ''' 1097 ABCSlurStart tokens always precede the notes in a slur. 1098 For nested slurs, each open parenthesis gets its own token. 1099 ''' 1100 def __init__(self, src): 1101 super().__init__(src) 1102 self.slurObj = None 1103 1104 def fillSlur(self): 1105 ''' 1106 Creates a spanner object for each open paren associated with a slur; 1107 these slurs are filled with notes until end parens are read. 1108 ''' 1109 from music21 import spanner 1110 self.slurObj = spanner.Slur() 1111 1112 1113class ABCParenStop(ABCToken): 1114 ''' 1115 A general parenthesis stop; 1116 comes at the end of a tuplet, slur, or dynamic marking. 1117 ''' 1118 1119 1120class ABCCrescStart(ABCToken): 1121 ''' 1122 ABCCrescStart tokens always precede the notes in a crescendo. 1123 These tokens coincide with the string "!crescendo("; 1124 the closing string "!crescendo)" counts as an ABCParenStop. 1125 ''' 1126 1127 def __init__(self, src): 1128 super().__init__(src) 1129 self.crescObj = None 1130 1131 def fillCresc(self): 1132 from music21 import dynamics 1133 self.crescObj = dynamics.Crescendo() 1134 1135 1136class ABCDimStart(ABCToken): 1137 ''' 1138 ABCDimStart tokens always precede the notes in a diminuendo. 1139 They function identically to ABCCrescStart tokens. 1140 ''' 1141 def __init__(self, src): # previous typo?: used to be __init 1142 super().__init__(src) 1143 self.dimObj = None 1144 1145 def fillDim(self): 1146 from music21 import dynamics 1147 self.dimObj = dynamics.Diminuendo() 1148 1149 1150class ABCStaccato(ABCToken): 1151 ''' 1152 ABCStaccato tokens "." precede a note or chord; 1153 they are a property of that note/chord. 1154 ''' 1155 1156 1157class ABCUpbow(ABCToken): 1158 ''' 1159 ABCStaccato tokens "." precede a note or chord; 1160 they are a property of that note/chord. 1161 ''' 1162 1163 1164class ABCDownbow(ABCToken): 1165 ''' 1166 ABCStaccato tokens "." precede a note or chord; 1167 they are a property of that note/chord. 1168 ''' 1169 1170 1171class ABCAccent(ABCToken): 1172 ''' 1173 ABCAccent tokens "K" precede a note or chord; 1174 they are a property of that note/chord. 1175 These appear as ">" in the output. 1176 ''' 1177 1178 1179class ABCStraccent(ABCToken): 1180 ''' 1181 ABCStraccent tokens "k" precede a note or chord; 1182 they are a property of that note/chord. 1183 These appear as "^" in the output. 1184 ''' 1185 1186 1187class ABCTenuto(ABCToken): 1188 ''' 1189 ABCTenuto tokens "M" precede a note or chord; 1190 they are a property of that note/chord. 1191 ''' 1192 1193 1194class ABCGraceStart(ABCToken): 1195 ''' 1196 Grace note start 1197 ''' 1198 1199 1200class ABCGraceStop(ABCToken): 1201 ''' 1202 Grace note end 1203 ''' 1204 1205 1206class ABCBrokenRhythmMarker(ABCToken): 1207 ''' 1208 Marks that rhythm is broken with '>>>' 1209 ''' 1210 1211 def __init__(self, src): 1212 super().__init__(src) 1213 self.data = None 1214 1215 def preParse(self): 1216 '''Called before context adjustments: need to have access to data 1217 1218 >>> brokenRhythm = abcFormat.ABCBrokenRhythmMarker('>>>') 1219 >>> brokenRhythm.preParse() 1220 >>> brokenRhythm.data 1221 '>>>' 1222 ''' 1223 self.data = self.src.strip() 1224 1225 1226class ABCNote(ABCToken): 1227 ''' 1228 A model of an ABCNote. 1229 1230 General usage requires multi-pass processing. After being tokenized, 1231 each ABCNote needs a number of attributes updates. Attributes to 1232 be updated after tokenizing, and based on the linear sequence of 1233 tokens: `inBar`, `inBeam` (not used), `inGrace`, 1234 `activeDefaultQuarterLength`, `brokenRhythmMarker`, and 1235 `activeKeySignature`. 1236 1237 The `chordSymbols` list stores one or more chord symbols (ABC calls 1238 these guitar chords) associated with this note. This attribute is 1239 updated when parse() is called. 1240 ''' 1241 def __init__(self, src='', carriedAccidental=None): 1242 super().__init__(src) 1243 1244 # store the ABC accidental string propagated in the measure that 1245 # must be applied to this note. Note must not be set if the 1246 # note already has an explicit accidental attached. (The explicit 1247 # accidental is now the one that will be carried forward.) 1248 self.carriedAccidental = carriedAccidental 1249 1250 # store chord string if connected to this note 1251 self.chordSymbols = [] 1252 1253 # context attributes 1254 self.inBar = None 1255 self.inBeam = None 1256 self.inGrace = None 1257 1258 # provide default duration from handler; may change during piece 1259 self.activeDefaultQuarterLength = None 1260 # store if a broken symbol applies; pair of symbol, position (left, right) 1261 self.brokenRhythmMarker = None 1262 1263 # store key signature for pitch processing; this is an m21 object 1264 self.activeKeySignature = None 1265 1266 # store a tuplet if active 1267 self.activeTuplet = None 1268 1269 # store a spanner if active 1270 self.applicableSpanners = [] 1271 1272 # store a tie if active 1273 self.tie = None 1274 1275 # store articulations if active 1276 self.articulations = [] 1277 1278 # set to True if a modification of key signature 1279 # set to False if an altered tone part of a Key 1280 self.accidentalDisplayStatus = None 1281 # determined during parse() based on if pitch chars are present 1282 self.isRest = None 1283 # pitch/ duration attributes for m21 conversion 1284 # set with parse() based on all other contextual 1285 self.pitchName = None # if None, a rest or chord 1286 self.quarterLength = None 1287 1288 @staticmethod 1289 def _splitChordSymbols(strSrc): 1290 ''' 1291 Splits chord symbols from other string characteristics. 1292 Return list of chord symbols and clean, remain chars 1293 1294 Staticmethod: 1295 1296 >>> an = abcFormat.ABCNote() 1297 >>> an._splitChordSymbols('"C"e2') 1298 (['"C"'], 'e2') 1299 >>> an._splitChordSymbols('b2') 1300 ([], 'b2') 1301 1302 >>> abcFormat.ABCNote._splitChordSymbols('"D7""D"d2') 1303 (['"D7"', '"D"'], 'd2') 1304 ''' 1305 if '"' in strSrc: 1306 chordSymbols = reChordSymbol.findall(strSrc) 1307 # might remove quotes from chord symbols here 1308 1309 # index of end of last match 1310 i = list(reChordSymbol.finditer(strSrc))[-1].end() 1311 return chordSymbols, strSrc[i:] 1312 else: 1313 return [], strSrc 1314 1315 def getPitchName( 1316 self, 1317 strSrc: str, 1318 forceKeySignature=None 1319 ) -> Tuple[Optional[str], Union[bool, None]]: 1320 ''' 1321 Given a note or rest string without a chord symbol, 1322 return a music21 pitch string or None (if a rest), 1323 and the accidental display status. This value is paired 1324 with an accidental display status. Pitch alterations, and 1325 accidental display status, are adjusted if a key is 1326 declared in the Note. 1327 1328 >>> an = abcFormat.ABCNote() 1329 >>> an.getPitchName('e2') 1330 ('E5', None) 1331 >>> an.getPitchName('C') 1332 ('C4', None) 1333 >>> an.getPitchName('B,,') 1334 ('B2', None) 1335 >>> an.getPitchName('C,') 1336 ('C3', None) 1337 >>> an.getPitchName('c') 1338 ('C5', None) 1339 >>> an.getPitchName("c'") 1340 ('C6', None) 1341 >>> an.getPitchName("c''") 1342 ('C7', None) 1343 >>> an.getPitchName("^g") 1344 ('G#5', True) 1345 >>> an.getPitchName("_g''") 1346 ('G-7', True) 1347 >>> an.getPitchName('=c') 1348 ('Cn5', True) 1349 1350 If pitch is a rest (z) then the Pitch name is None: 1351 1352 >>> an.getPitchName('z4') 1353 (None, None) 1354 1355 Grace note: 1356 1357 >>> an.getPitchName('{c}') 1358 ('C5', None) 1359 1360 1361 Given an active KeySignature object, the pitch name might 1362 change: 1363 1364 >>> an.activeKeySignature = key.KeySignature(3) 1365 >>> an.getPitchName('c') 1366 ('C#5', False) 1367 1368 1369 Illegal pitch names raise an ABCHandlerException 1370 1371 >>> an.getPitchName('x') 1372 Traceback (most recent call last): 1373 music21.abcFormat.ABCHandlerException: cannot find any pitch information in: 'x' 1374 ''' 1375 environLocal.printDebug(['getPitchName:', strSrc]) 1376 1377 # skip some articulations parsed with the pitch 1378 # some characters are errors in parsing or encoding not yet handled 1379 if len(strSrc) > 1 and strSrc[0] in 'uT': 1380 strSrc = strSrc[1:] 1381 strSrc = strSrc.replace('T', '') 1382 1383 try: 1384 name = rePitchName.findall(strSrc)[0] 1385 except IndexError: # no matches # pragma: no cover 1386 raise ABCHandlerException(f'cannot find any pitch information in: {strSrc!r}') 1387 1388 if name == 'z': 1389 return (None, None) # designates a rest 1390 1391 if forceKeySignature is not None: 1392 activeKeySignature = forceKeySignature 1393 else: # may be None 1394 activeKeySignature = self.activeKeySignature 1395 1396 try: # returns pStr, accidentalDisplayStatus 1397 return _pitchTranslationCache[(strSrc, 1398 self.carriedAccidental, 1399 str(activeKeySignature))] 1400 except KeyError: 1401 pass 1402 1403 if name.islower(): 1404 octave = 5 1405 else: 1406 octave = 4 1407 # look in source string for register modification 1408 octave -= strSrc.count(',') 1409 octave += strSrc.count("'") 1410 1411 # get an accidental string 1412 1413 accString = '' 1414 for dummy in range(strSrc.count('_')): 1415 accString += '-' # m21 symbols 1416 for dummy in range(strSrc.count('^')): 1417 accString += '#' # m21 symbols 1418 for dummy in range(strSrc.count('=')): 1419 accString += 'n' # m21 symbols 1420 1421 carriedAccString = '' 1422 if self.carriedAccidental: 1423 # No overriding accidental attached to this note 1424 # force carrying through the measure. 1425 for dummy in range(self.carriedAccidental.count('_')): 1426 carriedAccString += '-' # m21 symbols 1427 for dummy in range(self.carriedAccidental.count('^')): 1428 carriedAccString += '#' # m21 symbols 1429 for dummy in range(self.carriedAccidental.count('=')): 1430 carriedAccString += 'n' # m21 symbols 1431 1432 if carriedAccString and accString: 1433 raise ABCHandlerException('Carried accidentals not rendered moot.') 1434 # if there is an explicit accidental, regardless of key, it should 1435 # be shown: this will works for naturals well 1436 if carriedAccString: 1437 # An accidental carrying through the measure is supposed to be applied. 1438 # This will be set iff no explicit accidental is attached to the note. 1439 accidentalDisplayStatus = None 1440 elif accString != '': 1441 accidentalDisplayStatus = True 1442 # if we do not have a key signature, and have accidentals, set to None 1443 elif activeKeySignature is None: 1444 accidentalDisplayStatus = None 1445 # pitches are key dependent: accidentals are not given 1446 # if we have a key and find a name, that does not have a n, must be 1447 # altered 1448 else: 1449 alteredPitches = activeKeySignature.alteredPitches 1450 # just the steps, no accidentals 1451 alteredPitchSteps = [p.step.lower() for p in alteredPitches] 1452 # includes #, - 1453 alteredPitchNames = [p.name.lower() for p in alteredPitches] 1454 # environLocal.printDebug(['alteredPitches', alteredPitches]) 1455 1456 if name.lower() in alteredPitchSteps: 1457 # get the corresponding index in the name 1458 name = alteredPitchNames[alteredPitchSteps.index(name.lower())] 1459 # set to false, as do not need to show w/ key sig 1460 accidentalDisplayStatus = False 1461 1462 # making upper here, but this is not relevant 1463 if carriedAccString: 1464 pStr = f'{name.upper()}{carriedAccString}{octave}' 1465 else: 1466 pStr = f'{name.upper()}{accString}{octave}' 1467 1468 # store in global cache for faster speed 1469 _cacheKey = ( 1470 strSrc, 1471 self.carriedAccidental, 1472 str(activeKeySignature) 1473 ) 1474 1475 _pitchTranslationCache[_cacheKey] = (pStr, accidentalDisplayStatus) 1476 return (pStr, accidentalDisplayStatus) 1477 1478 def getQuarterLength(self, strSrc, forceDefaultQuarterLength=None) -> float: 1479 ''' 1480 Called with parse(), after context processing, to calculate duration 1481 1482 >>> an = abcFormat.ABCNote() 1483 >>> an.activeDefaultQuarterLength = 0.5 1484 >>> an.getQuarterLength('e2') 1485 1.0 1486 >>> an.getQuarterLength('G') 1487 0.5 1488 >>> an.getQuarterLength('=c/2') 1489 0.25 1490 >>> an.getQuarterLength('A3/2') 1491 0.75 1492 >>> an.getQuarterLength('A/') 1493 0.25 1494 1495 >>> an.getQuarterLength('A//') 1496 0.125 1497 >>> an.getQuarterLength('A///') 1498 0.0625 1499 1500 >>> an = abcFormat.ABCNote() 1501 >>> an.activeDefaultQuarterLength = 0.5 1502 >>> an.brokenRhythmMarker = ('>', 'left') 1503 >>> an.getQuarterLength('A') 1504 0.75 1505 >>> an.brokenRhythmMarker = ('>', 'right') 1506 >>> an.getQuarterLength('A') 1507 0.25 1508 1509 >>> an.brokenRhythmMarker = ('<<', 'left') 1510 >>> an.getQuarterLength('A') 1511 0.125 1512 >>> an.brokenRhythmMarker = ('<<', 'right') 1513 >>> an.getQuarterLength('A') 1514 0.875 1515 1516 >>> an.brokenRhythmMarker = ('<<<', 'left') 1517 >>> an.getQuarterLength('A') 1518 0.0625 1519 >>> an.brokenRhythmMarker = ('<<<', 'right') 1520 >>> an.getQuarterLength('A') 1521 0.9375 1522 1523 >>> an.getQuarterLength('A', forceDefaultQuarterLength=1) 1524 1.875 1525 ''' 1526 if forceDefaultQuarterLength is not None: 1527 activeDefaultQuarterLength = forceDefaultQuarterLength 1528 else: # may be None 1529 activeDefaultQuarterLength = self.activeDefaultQuarterLength 1530 1531 if activeDefaultQuarterLength is None: 1532 raise ABCTokenException( 1533 'cannot calculate quarter length without a default quarter length') 1534 1535 numStr = [] 1536 for c in strSrc: 1537 if c.isdigit() or c == '/': 1538 numStr.append(c) 1539 numStr = ''.join(numStr) 1540 numStr = numStr.strip() 1541 1542 # environLocal.printDebug(['numStr', numStr]) 1543 1544 # get default 1545 if numStr == '': 1546 ql = activeDefaultQuarterLength 1547 # if only, shorthand for /2 1548 elif numStr == '/': 1549 ql = activeDefaultQuarterLength * 0.5 1550 elif numStr == '//': 1551 ql = activeDefaultQuarterLength * 0.25 1552 elif numStr == '///': 1553 ql = activeDefaultQuarterLength * 0.125 1554 # if a half fraction 1555 elif numStr.startswith('/'): 1556 ql = activeDefaultQuarterLength / int(numStr.split('/')[1]) 1557 # uncommon usage: 3/ short for 3/2 1558 elif numStr.endswith('/'): 1559 n = int(numStr.split('/', maxsplit=1)[0].strip()) 1560 d = 2 1561 ql = activeDefaultQuarterLength * n / d 1562 # if we have two, this is usually an error 1563 elif numStr.count('/') == 2: # pragma: no cover 1564 environLocal.printDebug(['incorrectly encoded / unparsable duration:', numStr]) 1565 ql = 1 # provide a default 1566 1567 # assume we have a complete fraction 1568 elif '/' in numStr: 1569 n, d = numStr.split('/') 1570 n = int(n.strip()) 1571 d = int(d.strip()) 1572 ql = activeDefaultQuarterLength * n / d 1573 # not a fraction; a multiplier 1574 else: 1575 ql = activeDefaultQuarterLength * int(numStr) 1576 1577 if self.brokenRhythmMarker is not None: 1578 symbol, direction = self.brokenRhythmMarker 1579 if symbol == '>': 1580 modPair = (1.5, 0.5) 1581 elif symbol == '<': 1582 modPair = (0.5, 1.5) 1583 elif symbol == '>>': 1584 modPair = (1.75, 0.25) 1585 elif symbol == '<<': 1586 modPair = (0.25, 1.75) 1587 elif symbol == '>>>': 1588 modPair = (1.875, 0.125) 1589 elif symbol == '<<<': 1590 modPair = (0.125, 1.875) 1591 else: # pragma: no cover 1592 modPair = (1, 1) 1593 1594 # apply based on direction 1595 if direction == 'left': 1596 ql *= modPair[0] 1597 elif direction == 'right': 1598 ql *= modPair[1] 1599 1600 return ql 1601 1602 def parse( 1603 self, 1604 forceDefaultQuarterLength=None, 1605 forceKeySignature=None 1606 ) -> None: 1607 # environLocal.printDebug(['parse', self.src]) 1608 self.chordSymbols, nonChordSymStr = self._splitChordSymbols(self.src) 1609 # get pitch name form remaining string 1610 # rests will have a pitch name of None 1611 1612 try: 1613 pn, accDisp = self.getPitchName(nonChordSymStr, 1614 forceKeySignature=forceKeySignature) 1615 except ABCHandlerException: 1616 environLocal.warn(['Could not get pitch information from note: ', 1617 f'{nonChordSymStr}, assuming C']) 1618 pn = 'C' 1619 accDisp = False 1620 1621 self.pitchName, self.accidentalDisplayStatus = pn, accDisp 1622 1623 if self.pitchName is None: 1624 self.isRest = True 1625 else: 1626 self.isRest = False 1627 1628 self.quarterLength = self.getQuarterLength( 1629 nonChordSymStr, 1630 forceDefaultQuarterLength=forceDefaultQuarterLength) 1631 1632 # environLocal.printDebug(['ABCNote:', 'pitch name:', self.pitchName, 1633 # 'ql:', self.quarterLength]) 1634 1635 1636class ABCChord(ABCNote): 1637 ''' 1638 A representation of an ABC Chord, which contains within its delimiters individual notes. 1639 1640 A subclass of ABCNote. 1641 ''' 1642 1643 def __init__(self, src: str = ''): 1644 super().__init__(src) 1645 # store a list of component objects 1646 self.subTokens = [] 1647 1648 def parse(self, forceKeySignature=None, forceDefaultQuarterLength=None): 1649 ''' 1650 Handles the following types of chords: 1651 1652 * Chord without length modifier: [ceg] 1653 1654 * Chords with outer length modifier: [ceg]2, [ceg]/2 1655 1656 * Chords with inner length modifier: [c2e2g2], [c2eg] 1657 1658 * Chords with inner and outer length modifier: [c2e2g2]/2, [c/2e/2g/2]2 1659 ''' 1660 1661 self.chordSymbols, nonChordSymStr = self._splitChordSymbols(self.src) 1662 1663 # position of the closing bracket 1664 pos = nonChordSymStr.index(']') 1665 # Length modifier string behind the chord brackets 1666 outerLengthModifierStr = nonChordSymStr[pos + 1:] 1667 # String in the chord brackets 1668 tokenStr = nonChordSymStr[1:pos] 1669 1670 # environLocal.printDebug(['ABCChord:', nonChordSymStr, 'tokenStr', tokenStr, ' 1671 # outerLengthModifierStr', outerLengthModifierStr]) 1672 1673 # Get the outer chord length modifier if present 1674 outer_lengthModifier = self.getQuarterLength(outerLengthModifierStr, 1675 forceDefaultQuarterLength=1.0) 1676 1677 if forceKeySignature is not None: 1678 activeKeySignature = forceKeySignature 1679 else: # may be None 1680 activeKeySignature = self.activeKeySignature 1681 1682 # create a handler for processing internal chord notes 1683 ah = ABCHandler() 1684 # only tokenizing; not calling process() as these objects 1685 # have no metadata 1686 # may need to supply key? 1687 ah.tokenize(tokenStr) 1688 1689 inner_quarterLength = 0 1690 # tokens contained here are each ABCNote instances 1691 for t in ah.tokens: 1692 # environLocal.printDebug(['ABCChord: subTokens', t]) 1693 # parse any tokens individually, supply local data as necessary 1694 if isinstance(t, ABCNote): 1695 t.parse( 1696 forceDefaultQuarterLength=self.activeDefaultQuarterLength, 1697 forceKeySignature=activeKeySignature) 1698 1699 if t.isRest: 1700 continue 1701 1702 # get the quarter length from the sub-tokens 1703 # All the notes within a chord should normally have the same length, 1704 # but if not, the chord duration is that of the first note. 1705 if not inner_quarterLength: 1706 inner_quarterLength = t.quarterLength 1707 1708 self.subTokens.append(t) 1709 1710 1711 # When both inside and outside the chord length modifiers are used, 1712 # they should be multiplied. Example: [C2E2G2]3 has the same meaning as [CEG]6. 1713 self.quarterLength = outer_lengthModifier * inner_quarterLength 1714 1715 1716# ------------------------------------------------------------------------------ 1717class ABCHandler: 1718 ''' 1719 An ABCHandler is able to divide elements of a character stream into objects and handle 1720 store in a list, and passes global information to components 1721 1722 Optionally, specify the (major, minor, patch) version of ABC to process-- 1723 e.g., (1.2.0). If not set, default ABC 1.3 parsing is performed. 1724 1725 If lineBreaksDefinePhrases is True then new lines within music elements 1726 define new phrases. This is useful for parsing extra information from 1727 the Essen Folksong repertory 1728 1729 New in v6.3 -- lineBreaksDefinePhrases -- does not yet do anything 1730 ''' 1731 def __init__(self, abcVersion=None, lineBreaksDefinePhrases=False): 1732 # tokens are ABC objects import n a linear stream 1733 self.abcVersion = abcVersion 1734 self.abcDirectives = {} 1735 self.tokens = [] 1736 self.activeParens = [] 1737 self.activeSpanners = [] 1738 self.lineBreaksDefinePhrases = lineBreaksDefinePhrases 1739 self.pos = -1 1740 self.skipAhead = 0 1741 self.isFirstComment = True 1742 self.strSrc = '' 1743 self.srcLen = len(self.strSrc) # just documenting this. 1744 self.currentCollectStr = '' 1745 1746 @staticmethod 1747 def _getLinearContext(source, i: int) -> Tuple[Any, Any, Any, Any]: 1748 ''' 1749 Find the local context of a string or iterable of objects 1750 beginning at a particular index. 1751 1752 Returns a tuple of charPrev, charThis, charNext, charNextNext. 1753 1754 Staticmethod 1755 1756 >>> ah = abcFormat.ABCHandler() 1757 >>> ah._getLinearContext('12345', 0) 1758 (None, '1', '2', '3') 1759 >>> ah._getLinearContext('12345', 1) 1760 ('1', '2', '3', '4') 1761 >>> abcFormat.ABCHandler._getLinearContext('12345', 3) 1762 ('3', '4', '5', None) 1763 >>> abcFormat.ABCHandler._getLinearContext('12345', 4) 1764 ('4', '5', None, None) 1765 1766 >>> abcFormat.ABCHandler._getLinearContext([32, None, 8, 11, 53], 4) 1767 (11, 53, None, None) 1768 >>> ah._getLinearContext([32, None, 8, 11, 53], 2) 1769 (None, 8, 11, 53) 1770 >>> ah._getLinearContext([32, None, 8, 11, 53], 0) 1771 (None, 32, None, 8) 1772 ''' 1773 # Note: this is performance critical method 1774 lastIndex = len(source) - 1 1775 if i > lastIndex: 1776 raise ABCHandlerException(f'bad index value {i}, max is {lastIndex}') 1777 1778 # find local area of iterable 1779 cPrev = None 1780 if i > 0: 1781 cPrev = source[i - 1] 1782 1783 # set current characters or items 1784 c = source[i] 1785 1786 cNext = None 1787 if i < len(source) - 1: 1788 cNext = source[i + 1] 1789 1790 # get 2 entries forward 1791 cNextNext = None 1792 if i < len(source) - 2: 1793 cNextNext = source[i + 2] 1794 1795 return cPrev, c, cNext, cNextNext 1796 # return cPrevNotSpace, cPrev, c, cNext, cNextNotSpace, cNextNext 1797 1798 @staticmethod 1799 def _getNextLineBreak(strSrc: str, i: int) -> Optional[int]: 1800 r''' 1801 Return index of next line break after character i. 1802 1803 Staticmethod 1804 1805 >>> ah = abcFormat.ABCHandler() 1806 >>> inputString = 'de we\n wer bfg\n' 1807 >>> ah._getNextLineBreak(inputString, 0) 1808 6 1809 >>> inputString[0:6] 1810 'de we' 1811 1812 from last line break 1813 1814 >>> abcFormat.ABCHandler._getNextLineBreak(inputString, 6) 1815 15 1816 >>> inputString[ah._getNextLineBreak(inputString, 0):] 1817 '\n wer bfg\n' 1818 ''' 1819 lastIndex = len(strSrc) - 1 1820 for j in range(i + 1, lastIndex + 1): 1821 if strSrc[j] == '\n': 1822 return j 1823 return lastIndex + 1 1824 1825 @staticmethod 1826 def barlineTokenFilter(token: str) -> List[ABCBar]: 1827 ''' 1828 Some single barline tokens are better replaced 1829 with two tokens. This method, given a token, 1830 returns a list of tokens. If there is no change 1831 necessary, the provided token will be returned in the list. 1832 1833 A staticmethod. Call on the class itself. 1834 1835 >>> abcFormat.ABCHandler.barlineTokenFilter('::') 1836 [<music21.abcFormat.ABCBar ':|'>, <music21.abcFormat.ABCBar '|:'>] 1837 1838 >>> abcFormat.ABCHandler.barlineTokenFilter('|2') 1839 [<music21.abcFormat.ABCBar '|'>, <music21.abcFormat.ABCBar '[2'>] 1840 1841 >>> abcFormat.ABCHandler.barlineTokenFilter(':|1') 1842 [<music21.abcFormat.ABCBar ':|'>, <music21.abcFormat.ABCBar '[1'>] 1843 1844 If nothing matches, the original token is returned as an ABCBar object: 1845 1846 >>> abcFormat.ABCHandler.barlineTokenFilter('hi') 1847 [<music21.abcFormat.ABCBar 'hi'>] 1848 ''' 1849 barTokens: List[ABCBar] = [] 1850 if token == '::': 1851 # create a start and and an end 1852 barTokens.append(ABCBar(':|')) 1853 barTokens.append(ABCBar('|:')) 1854 elif token == '|1': 1855 # create a start and and an end 1856 barTokens.append(ABCBar('|')) 1857 barTokens.append(ABCBar('[1')) 1858 elif token == '|2': 1859 # create a start and and an end 1860 barTokens.append(ABCBar('|')) 1861 barTokens.append(ABCBar('[2')) 1862 elif token == ':|1': 1863 # create a start and and an end 1864 barTokens.append(ABCBar(':|')) 1865 barTokens.append(ABCBar('[1')) 1866 elif token == ':|2': 1867 # create a start and and an end 1868 barTokens.append(ABCBar(':|')) 1869 barTokens.append(ABCBar('[2')) 1870 else: # append unaltered 1871 barTokens.append(ABCBar(token)) 1872 return barTokens 1873 1874 # -------------------------------------------------------------------------- 1875 # token processing 1876 1877 def _accidentalPropagation(self) -> str: 1878 ''' 1879 Determine how accidentals should 'carry through the measure.' 1880 1881 >>> ah = abcFormat.ABCHandler(abcVersion=(1, 3, 0)) 1882 >>> ah._accidentalPropagation() 1883 'not' 1884 >>> ah = abcFormat.ABCHandler(abcVersion=(2, 0, 0)) 1885 >>> ah._accidentalPropagation() 1886 'pitch' 1887 ''' 1888 minVersion = (2, 0, 0) 1889 if not self.abcVersion or self.abcVersion < minVersion: 1890 return 'not' 1891 if 'propagate-accidentals' in self.abcDirectives: 1892 return self.abcDirectives['propagate-accidentals'] 1893 return 'pitch' # Default per abc 2.1 standard 1894 1895 def parseCommentForVersionInformation(self, commentLine: str): 1896 ''' 1897 If this is the first comment then searches for a version 1898 match and set it as .abcVersion 1899 1900 If not isFirstComment then does nothing: 1901 1902 >>> ah = abcFormat.ABCHandler() 1903 >>> ah.abcVersion is None 1904 True 1905 >>> ah.isFirstComment 1906 True 1907 1908 >>> ah.parseCommentForVersionInformation('%abc-2.3.2') 1909 >>> ah.abcVersion 1910 (2, 3, 2) 1911 >>> ah.isFirstComment 1912 False 1913 1914 Now will do nothing since isFirstComment is False 1915 1916 >>> ah.parseCommentForVersionInformation('%abc-4.9.7') 1917 >>> ah.abcVersion 1918 (2, 3, 2) 1919 ''' 1920 if not self.isFirstComment: 1921 return 1922 self.isFirstComment = False 1923 verMats = reAbcVersion.match(commentLine) 1924 if verMats: 1925 abcMajor = int(verMats.group(2)) 1926 abcMinor = int(verMats.group(3)) 1927 if verMats.group(4): 1928 abcPatch = int(verMats.group(4)) 1929 else: 1930 abcPatch = 0 1931 verTuple = (abcMajor, abcMinor, abcPatch) 1932 self.abcVersion = verTuple 1933 1934 def processComment(self): 1935 r''' 1936 Processes the comment at self.pos in self.strSrc, setting self.skipAhead, 1937 possibly self.abcVersion, and self.abcDirectives for the directiveKey. 1938 1939 TODO: store the comment in the stream also. 1940 1941 >>> from textwrap import dedent 1942 >>> ah = abcFormat.ABCHandler() 1943 >>> data = dedent(""" 1944 ... Hello % this is a comment 1945 ... Bye 1946 ... """) 1947 >>> ah.strSrc = data 1948 >>> ah.pos = 6 1949 >>> ah.processComment() 1950 >>> ah.skipAhead 1951 19 1952 >>> len(' this is a comment\n') 1953 19 1954 ''' 1955 self.skipAhead = self._getNextLineBreak( 1956 self.strSrc, self.pos 1957 ) - (self.pos + 1) 1958 commentLine = self.strSrc[self.pos:self.pos + self.skipAhead + 1] 1959 self.parseCommentForVersionInformation(commentLine) 1960 directiveMatches = reDirective.match(commentLine) 1961 if directiveMatches: 1962 directiveKey = directiveMatches.group(1) 1963 directiveValue = directiveMatches.group(2) 1964 self.abcDirectives[directiveKey] = directiveValue 1965 # environLocal.printDebug(['got comment:', repr(self.strSrc[i:j + 1])]) 1966 1967 @staticmethod 1968 def startsMetadata(c: str, cNext: Optional[str], cNextNext: Optional[str]) -> bool: 1969 ''' 1970 Returns True if this context describes the start of a metadata section, like 1971 1972 A:something 1973 1974 Metadata: capital letter, with next char as ':' and some following character 1975 1976 >>> ah = abcFormat.ABCHandler 1977 >>> ah.startsMetadata('A', ':', 's') 1978 True 1979 1980 lowercase w: is a special case for lyric defs 1981 1982 >>> ah.startsMetadata('w', ':', 's') 1983 True 1984 1985 Following char must be ":" 1986 1987 >>> ah.startsMetadata('A', ' ', 's') 1988 False 1989 1990 Pipe after colon indicates not metadata (bar info). 1991 For example need to not misinterpret repeat bars as metadata 1992 e.g. `dAG FED:|2 dAG FGA|` 1993 1994 this is incorrect, but we can avoid it by 1995 looking for a leading pipe and returning False 1996 1997 >>> ah.startsMetadata('A', ':', '|') 1998 False 1999 2000 >>> ah.startsMetadata('A', ':', None) 2001 False 2002 ''' 2003 if cNext != ':': 2004 return False 2005 elif cNextNext is None: 2006 return False 2007 elif cNextNext == '|': 2008 return False 2009 elif c == 'w': 2010 return True # special case, w:... 2011 elif c.isalpha() and c.isupper(): 2012 return True 2013 return False 2014 2015 2016 def tokenize(self, strSrc: str) -> None: 2017 ''' 2018 Walk the abc string, creating ABC objects along the way. 2019 2020 This may be called separately from process(), in the case 2021 that pre/post parse processing is not needed. 2022 2023 >>> abch = abcFormat.ABCHandler() 2024 >>> abch.tokens 2025 [] 2026 >>> abch.tokenize('X: 1') 2027 >>> abch.tokens 2028 [<music21.abcFormat.ABCMetadata 'X: 1'>] 2029 2030 >>> abch = abcFormat.ABCHandler() 2031 >>> abch.tokenize('(6f') 2032 >>> abch.tokens 2033 [<music21.abcFormat.ABCTuplet '(6'>, <music21.abcFormat.ABCNote 'f'>] 2034 2035 >>> abch = abcFormat.ABCHandler() 2036 >>> abch.tokenize('(6:4f') 2037 >>> abch.tokens 2038 [<music21.abcFormat.ABCTuplet '(6:4'>, <music21.abcFormat.ABCNote 'f'>] 2039 2040 >>> abch = abcFormat.ABCHandler() 2041 >>> abch.tokenize('(6:4:2f') 2042 >>> abch.tokens 2043 [<music21.abcFormat.ABCTuplet '(6:4:2'>, <music21.abcFormat.ABCNote 'f'>] 2044 2045 >>> abch = abcFormat.ABCHandler() 2046 >>> abch.tokenize('(6::2f') 2047 >>> abch.tokens 2048 [<music21.abcFormat.ABCTuplet '(6::2'>, <music21.abcFormat.ABCNote 'f'>] 2049 ''' 2050 self.srcLen = len(strSrc) 2051 self.strSrc = strSrc 2052 self.pos = -1 2053 self.currentCollectStr = '' 2054 self.skipAhead = 0 2055 # noinspection SpellCheckingInspection 2056 accidentalsAndDecorations = '.~^=_HLMOPSTuv' 2057 accidentals = '^=_' 2058 2059 activeChordSymbol = '' # accumulate, then prepend 2060 accidentalized = {} 2061 accidental = None 2062 abcPitch = None # ABC substring defining any pitch within the current token 2063 self.isFirstComment = True 2064 2065 while self.pos < self.srcLen - 1: 2066 self.pos += 1 2067 self.pos += self.skipAhead 2068 self.skipAhead = 0 2069 if self.pos > self.srcLen - 1: 2070 break 2071 2072 q = self._getLinearContext(self.strSrc, self.pos) 2073 unused_cPrev, c, cNext, cNextNext = q 2074 # cPrevNotSpace, cPrev, c, cNext, cNextNotSpace, cNextNext = q 2075 2076 # comment lines, also encoding defs 2077 if c == '%': 2078 self.processComment() 2079 continue 2080 2081 if self.startsMetadata(c, cNext, cNextNext): 2082 # collect until end of line; add one to get line break 2083 j = self._getNextLineBreak(self.strSrc, self.pos) 2084 self.skipAhead = j - (self.pos + 1) 2085 self.currentCollectStr = self.strSrc[self.pos:j].strip() 2086 # environLocal.printDebug(['got metadata:', repr(self.currentCollectStr)]) 2087 self.tokens.append(ABCMetadata(self.currentCollectStr)) 2088 continue 2089 2090 # get bars: if not a space and not alphanumeric 2091 if not c.isspace() and not c.isalnum() and c not in ('~', '('): 2092 matchBars = False 2093 for barIndex in range(len(ABC_BARS)): 2094 # first of bars tuple is symbol to match 2095 # three possible sizes of bar indications: 3, 2, 1 2096 barTokenArchetype = ABC_BARS[barIndex][0] 2097 if len(barTokenArchetype) == 3: 2098 if cNextNext is not None and (c + cNext + cNextNext == barTokenArchetype): 2099 self.skipAhead = 2 2100 matchBars = True 2101 break 2102 elif cNext is not None and (len(barTokenArchetype) == 2): 2103 if c + cNext == barTokenArchetype: 2104 self.skipAhead = 1 2105 matchBars = True 2106 break 2107 elif len(barTokenArchetype) == 1: 2108 if c == barTokenArchetype: 2109 self.skipAhead = 0 2110 matchBars = True 2111 break 2112 if matchBars is True: 2113 accidentalized = {} 2114 accidental = None 2115 j = self.pos + self.skipAhead + 1 2116 self.currentCollectStr = self.strSrc[self.pos:j] 2117 # filter and replace with 2 tokens if necessary 2118 for tokenSub in self.barlineTokenFilter(self.currentCollectStr): 2119 self.tokens.append(tokenSub) 2120 # environLocal.printDebug(['got bars:', repr(self.currentCollectStr)]) 2121 # if self.currentCollectStr == '::': 2122 # # create a start and and an end 2123 # self.tokens.append(ABCBar(':|')) 2124 # self.tokens.append(ABCBar('|:')) 2125 # else: 2126 # self.tokens.append(ABCBar(self.currentCollectStr)) 2127 continue 2128 2129 # get tuplet indicators: (2, (3, (p:q:r or (3:: 2130 if c == '(' and cNext is not None and cNext.isdigit(): 2131 self.skipAhead = 1 2132 j = self.pos + self.skipAhead + 1 # always two characters 2133 unused1, possibleColon, qChar, unused2 = self._getLinearContext(self.strSrc, j) 2134 if possibleColon == ':': 2135 j += 1 2136 self.skipAhead += 1 2137 if qChar is not None and qChar.isdigit(): 2138 j += 1 2139 self.skipAhead += 1 2140 unused1, possibleColon, rChar, unused2 = self._getLinearContext(self.strSrc, j) 2141 if possibleColon == ':': 2142 j += 1 # include the r characters 2143 self.skipAhead += 1 2144 if rChar is not None and rChar.isdigit(): 2145 j += 1 2146 self.skipAhead += 1 2147 2148 self.currentCollectStr = self.strSrc[self.pos:j] 2149 # environLocal.printDebug(['got tuplet start:', repr(self.currentCollectStr)]) 2150 self.tokens.append(ABCTuplet(self.currentCollectStr)) 2151 continue 2152 2153 # get broken rhythm modifiers: < or >, >>, up to <<< 2154 if c in '<>': 2155 j = self.pos + 1 2156 while j < self.srcLen - 1 and self.strSrc[j] in '<>': 2157 j += 1 2158 self.currentCollectStr = self.strSrc[self.pos:j] 2159 # environLocal.printDebug( 2160 # ['got bidrectional rhythm mod:', repr(self.currentCollectStr)]) 2161 self.tokens.append(ABCBrokenRhythmMarker(self.currentCollectStr)) 2162 self.skipAhead = j - (self.pos + 1) 2163 continue 2164 2165 # get dynamics. skip over the open paren to avoid confusion. 2166 # NB: Nested crescendos are not an issue (not proper grammar). 2167 if c == '!': 2168 exclaimDict = {'!crescendo(!': ABCCrescStart, 2169 '!crescendo)!': ABCParenStop, 2170 '!diminuendo(!': ABCDimStart, 2171 '!diminuendo)!': ABCParenStop, 2172 } 2173 j = self.pos + 1 2174 while j < self.pos + 20 and j < self.srcLen: # a reasonable upper bound 2175 if self.strSrc[j] == '!': 2176 if self.strSrc[self.pos:j + 1] in exclaimDict: 2177 exclaimClass = exclaimDict[self.strSrc[self.pos:j + 1]] 2178 exclaimObject = exclaimClass(c) 2179 self.tokens.append(exclaimObject) 2180 self.skipAhead = j - self.pos # not + 1 2181 break 2182 # NB: We're currently skipping over all other '!' expressions 2183 else: 2184 self.skipAhead = j - self.pos # not + 1 2185 break 2186 j += 1 2187 # not found, continue... 2188 continue 2189 2190 # get slurs, ensuring that they're not confused for tuplets 2191 if c == '(' and cNext is not None and not cNext.isdigit(): 2192 self.tokens.append(ABCSlurStart(c)) 2193 continue 2194 2195 # get slur/tuplet ending; treat it as a general parenthesis stop 2196 if c == ')': 2197 self.tokens.append(ABCParenStop(c)) 2198 continue 2199 2200 # get ties between two notes 2201 if c == '-': 2202 self.tokens.append(ABCTie(c)) 2203 continue 2204 2205 # get chord symbols / guitar chords; collected and joined with 2206 # chord or notes 2207 if c == '"': 2208 j = self.pos + 1 2209 while j < self.srcLen - 1 and self.strSrc[j] != '"': 2210 j += 1 2211 j += 1 # need character that caused break 2212 # there may be more than one chord symbol: need to accumulate 2213 activeChordSymbol += self.strSrc[self.pos:j] 2214 # environLocal.printDebug(['got chord symbol:', repr(activeChordSymbol)]) 2215 self.skipAhead = j - (self.pos + 1) 2216 continue 2217 2218 # get chords 2219 if c == '[': 2220 j = self.pos + 1 2221 2222 # find closing chord bracket 2223 while j < self.srcLen - 1 and self.strSrc[j] != ']': 2224 j += 1 2225 2226 j += 1 # need character that caused break 2227 2228 # find outer chord length modifier 2229 while j < self.srcLen and (self.strSrc[j].isdigit() or self.strSrc[j] in '/'): 2230 j += 1 2231 2232 # prepend chord symbol 2233 if activeChordSymbol != '': 2234 self.currentCollectStr = activeChordSymbol + self.strSrc[self.pos:j] 2235 activeChordSymbol = '' # reset 2236 else: 2237 self.currentCollectStr = self.strSrc[self.pos:j] 2238 2239 # environLocal.printDebug(['got chord:', repr(self.currentCollectStr)]) 2240 self.tokens.append(ABCChord(self.currentCollectStr)) 2241 self.skipAhead = j - (self.pos + 1) 2242 # TODO: Chords need to be aware of accidentals too. 2243 # Also what happens to prefixes and suffixes attached to chords, 2244 # like ties. 2245 continue 2246 2247 if c == '.': 2248 self.tokens.append(ABCStaccato(c)) 2249 continue 2250 2251 if c == 'u': 2252 self.tokens.append(ABCUpbow(c)) 2253 continue 2254 2255 if c == '{': 2256 self.tokens.append(ABCGraceStart(c)) 2257 continue 2258 2259 if c == '}': 2260 self.tokens.append(ABCGraceStop(c)) 2261 continue 2262 2263 if c == 'v': 2264 self.tokens.append(ABCDownbow(c)) 2265 continue 2266 2267 if c == 'K': 2268 self.tokens.append(ABCAccent(c)) 2269 continue 2270 2271 if c == 'k': 2272 self.tokens.append(ABCStraccent(c)) 2273 continue 2274 2275 if c == 'M': 2276 self.tokens.append(ABCTenuto(c)) 2277 continue 2278 2279 # get the start of a note event: alpha, decoration, or accidental 2280 if c.isalpha() or c in '~^=_': 2281 # condition where we start with an alpha that is not an alpha 2282 # that comes before a pitch indication 2283 # From the 2.2 draft standard, we see the following "decorations" 2284 # defined: 2285 # . staccato mark 2286 # ~ Irish roll 2287 # H fermata 2288 # L accent or emphasis 2289 # M lower mordent 2290 # O coda 2291 # P upper mordent 2292 # S segno 2293 # T trill 2294 # u up-bow 2295 # v down-bow 2296 # 2297 # Accidentals are these: 2298 # ^ sharp 2299 # ^^ double-sharp 2300 # = natural 2301 # _ flat 2302 # __ double-flat 2303 foundPitchAlpha = c.isalpha() and c not in accidentalsAndDecorations 2304 if foundPitchAlpha: 2305 abcPitch = c 2306 if c in accidentals: 2307 accidental = c 2308 j = self.pos + 1 2309 2310 while j <= self.srcLen - 1: 2311 # if we have not found pitch alpha 2312 # decorations and/or accidentals may precede note names 2313 if not foundPitchAlpha and self.strSrc[j] in accidentalsAndDecorations: 2314 j += 1 2315 if self.strSrc[j] in accidentals: 2316 accidental += self.strSrc[j] 2317 continue 2318 # only allow one pitch alpha to be a continue condition 2319 elif (not foundPitchAlpha and self.strSrc[j].isalpha() 2320 # noinspection SpellCheckingInspection 2321 and self.strSrc[j] not in '~wuvhHLTSN'): 2322 foundPitchAlpha = True 2323 abcPitch = self.strSrc[j] 2324 j += 1 2325 continue 2326 # continue conditions after alpha: 2327 # , register modification (, ') or number, rhythm indication 2328 # number, /, 2329 elif self.strSrc[j].isdigit() or self.strSrc[j] in ',/,\'': 2330 if self.strSrc[j] in ',\'': # Register (octave) modification 2331 abcPitch += self.strSrc[j] 2332 j += 1 2333 continue 2334 else: # space, all else: break 2335 break 2336 # prepend chord symbol 2337 if activeChordSymbol != '': 2338 self.currentCollectStr = activeChordSymbol + self.strSrc[self.pos:j] 2339 activeChordSymbol = '' # reset 2340 else: 2341 self.currentCollectStr = self.strSrc[self.pos:j] 2342 # environLocal.printDebug(['got note event:', repr(self.currentCollectStr)]) 2343 2344 # NOTE: skipping a number of articulations and other markers 2345 # that are not yet supported 2346 # some collections here are not yet supported; others may be 2347 # the result of errors in encoded files 2348 # v is up bow; might be: "^Segno"v which also should be dropped 2349 # H is fermata 2350 # . dot may be staccato, but should be attached to pitch 2351 if self.currentCollectStr in ('w', 'u', 'v', 'v.', 'h', 'H', 'vk', 2352 'uk', 'U', '~', 2353 '.', '=', 'V', 'v.', 'S', 's', 2354 'i', 'I', 'ui', 'u.', 'Q', 'Hy', 'Hx', 2355 'r', 'm', 'M', 'n', 'N', 'o', 'O', 'P', 2356 'l', 'L', 'R', 2357 'y', 'T', 't', 'x', 'Z'): 2358 pass 2359 # these are bad chords, or other problematic notations like 2360 # "D.C."x 2361 elif (self.currentCollectStr.startswith('"') 2362 and (self.currentCollectStr[-1] in ('u', 'v', 'k', 'K', 'Q', '.', 2363 'y', 'T', 'w', 'h', 'x',) 2364 or self.currentCollectStr.endswith('v.'))): 2365 pass 2366 elif (self.currentCollectStr.startswith('x') 2367 or self.currentCollectStr.startswith('H') 2368 or self.currentCollectStr.startswith('Z')): 2369 pass 2370 # not sure what =20 refers to 2371 elif (len(self.currentCollectStr) > 1 2372 and self.currentCollectStr.startswith('=') 2373 and self.currentCollectStr[1].isdigit()): 2374 pass 2375 # only let valid self.currentCollectStr strings be parsed 2376 elif abcPitch: 2377 pitchClass = abcPitch[0].upper() 2378 carriedAccidental = None 2379 propagation = self._accidentalPropagation() 2380 if accidental: 2381 # Remember the active accidentals in the measure 2382 if propagation == 'octave': 2383 accidentalized[abcPitch] = accidental 2384 elif propagation == 'pitch': 2385 accidentalized[pitchClass] = accidental 2386 accidental = None 2387 else: 2388 if propagation == 'pitch' and pitchClass in accidentalized: 2389 carriedAccidental = accidentalized[pitchClass] 2390 elif propagation == 'octave' and abcPitch in accidentalized: 2391 carriedAccidental = accidentalized[abcPitch] 2392 abcNote = ABCNote(self.currentCollectStr, carriedAccidental=carriedAccidental) 2393 self.tokens.append(abcNote) 2394 else: 2395 self.tokens.append(ABCNote(self.currentCollectStr)) 2396 2397 self.skipAhead = j - (self.pos + 1) 2398 continue 2399 # look for white space: can be used to determine beam groups 2400 # no action: normal continuation of 1 char 2401 pass 2402 2403 def tokenProcess(self): 2404 ''' 2405 Process all token objects. First, calls preParse(), then 2406 does context assignments, then calls parse(). 2407 ''' 2408 # need a key object to get altered pitches 2409 from music21 import key 2410 2411 # pre-parse : call on objects that need preliminary processing 2412 # metadata, for example, is parsed 2413 # lastTimeSignature = None 2414 for t in self.tokens: 2415 # environLocal.printDebug(['tokenProcess: calling preParse()', t.src]) 2416 t.preParse() 2417 2418 # context: iterate through tokens, supplying contextual data 2419 # as necessary to appropriate objects 2420 lastDefaultQL = None 2421 lastKeySignature = None 2422 lastTimeSignatureObj = None # an m21 object 2423 lastTupletToken = None # a token obj; keeps count of usage 2424 lastTieToken = None 2425 lastStaccToken = None 2426 lastUpToken = None 2427 lastDownToken = None 2428 lastAccToken = None 2429 lastStrAccToken = None 2430 lastTenutoToken = None 2431 lastGraceToken = None 2432 lastNoteToken = None 2433 2434 for i in range(len(self.tokens)): 2435 # get context of tokens 2436 q = self._getLinearContext(self.tokens, i) 2437 tPrev, t, tNext, unused_tNextNext = q 2438 # tPrevNotSpace, tPrev, t, tNext, tNextNotSpace, tNextNext = q 2439 # environLocal.printDebug(['tokenProcess: calling parse()', t]) 2440 2441 if isinstance(t, ABCMetadata): 2442 if t.isMeter(): 2443 lastTimeSignatureObj = t.getTimeSignatureObject() 2444 # restart matching conditions; match meter twice ok 2445 if t.isDefaultNoteLength() or (t.isMeter() and lastDefaultQL is None): 2446 lastDefaultQL = t.getDefaultQuarterLength() 2447 elif t.isKey(): 2448 sharpCount, mode = t.getKeySignatureParameters() 2449 lastKeySignature = key.KeySignature(sharpCount) 2450 if mode not in (None, ''): 2451 lastKeySignature = lastKeySignature.asKey(mode) 2452 2453 if t.isReferenceNumber(): 2454 # reset any spanners or parens at the end of any piece 2455 # in case they aren't closed. 2456 self.activeParens = [] 2457 self.activeSpanners = [] 2458 continue 2459 # broken rhythms need to be applied to previous and next notes 2460 if isinstance(t, ABCBrokenRhythmMarker): 2461 if (isinstance(tPrev, ABCNote) 2462 and isinstance(tNext, ABCNote)): 2463 # environLocal.printDebug(['tokenProcess: got broken rhythm marker', t.src]) 2464 tPrev.brokenRhythmMarker = (t.data, 'left') 2465 tNext.brokenRhythmMarker = (t.data, 'right') 2466 else: 2467 environLocal.printDebug( 2468 ['broken rhythm marker ' 2469 + f'({t.src}) not positioned between two notes or chords']) 2470 2471 # need to update tuplets with currently active meter 2472 if isinstance(t, ABCTuplet): 2473 t.updateRatio(lastTimeSignatureObj) 2474 # set number of notes that will be altered 2475 # might need to do this with ql values, or look ahead to nxt 2476 # token 2477 t.updateNoteCount() 2478 lastTupletToken = t 2479 self.activeParens.append('Tuplet') 2480 2481 # notes within slur marks need to be added to the spanner 2482 if isinstance(t, ABCSlurStart): 2483 t.fillSlur() 2484 self.activeSpanners.append(t.slurObj) 2485 self.activeParens.append('Slur') 2486 elif isinstance(t, ABCParenStop): 2487 if self.activeParens: 2488 p = self.activeParens.pop() 2489 if p in ('Slur', 'Crescendo', 'Diminuendo'): 2490 self.activeSpanners.pop() 2491 2492 if isinstance(t, ABCTie): 2493 # tPrev is usually an ABCNote but may be a GraceStop. 2494 if lastNoteToken and lastNoteToken.tie == 'stop': 2495 lastNoteToken.tie = 'continue' 2496 elif lastNoteToken: 2497 lastNoteToken.tie = 'start' 2498 lastTieToken = t 2499 2500 if isinstance(t, ABCStaccato): 2501 lastStaccToken = t 2502 2503 if isinstance(t, ABCUpbow): 2504 lastUpToken = t 2505 2506 if isinstance(t, ABCDownbow): 2507 lastDownToken = t 2508 2509 if isinstance(t, ABCAccent): 2510 lastAccToken = t 2511 2512 if isinstance(t, ABCStraccent): 2513 lastStrAccToken = t 2514 2515 if isinstance(t, ABCTenuto): 2516 lastTenutoToken = t 2517 2518 if isinstance(t, ABCCrescStart): 2519 t.fillCresc() 2520 self.activeSpanners.append(t.crescObj) 2521 self.activeParens.append('Crescendo') 2522 2523 if isinstance(t, ABCDimStart): 2524 t.fillDim() 2525 self.activeSpanners.append(t.dimObj) 2526 self.activeParens.append('Diminuendo') 2527 2528 if isinstance(t, ABCGraceStart): 2529 lastGraceToken = t 2530 2531 if isinstance(t, ABCGraceStop): 2532 lastGraceToken = None 2533 2534 # ABCChord inherits ABCNote, thus getting note is enough for both 2535 if isinstance(t, (ABCNote, ABCChord)): 2536 if lastDefaultQL is None: 2537 raise ABCHandlerException( 2538 'no active default note length provided for note processing. ' 2539 + f'tPrev: {tPrev}, t: {t}, tNext: {tNext}' 2540 ) 2541 t.activeDefaultQuarterLength = lastDefaultQL 2542 t.activeKeySignature = lastKeySignature 2543 t.applicableSpanners = self.activeSpanners[:] # fast copy of a list 2544 # ends ties one note after they begin 2545 if lastTieToken is not None: 2546 t.tie = 'stop' 2547 lastTieToken = None 2548 if lastStaccToken is not None: 2549 t.articulations.append('staccato') 2550 lastStaccToken = None 2551 if lastUpToken is not None: 2552 t.articulations.append('upbow') 2553 lastUpToken = None 2554 if lastDownToken is not None: 2555 t.articulations.append('downbow') 2556 lastDownToken = None 2557 if lastAccToken is not None: 2558 t.articulations.append('accent') 2559 lastAccToken = None 2560 if lastStrAccToken is not None: 2561 t.articulations.append('strongaccent') 2562 lastStrAccToken = None 2563 if lastTenutoToken is not None: 2564 t.articulations.append('tenuto') 2565 lastTenutoToken = None 2566 if lastGraceToken is not None: 2567 t.inGrace = True 2568 if lastTupletToken is None: 2569 pass 2570 elif lastTupletToken.noteCount == 0: 2571 lastTupletToken = None # clear, no longer needed 2572 else: 2573 lastTupletToken.noteCount -= 1 # decrement 2574 # add a reference to the note 2575 t.activeTuplet = lastTupletToken.tupletObj 2576 lastNoteToken = t 2577 2578 # parse : call methods to set attributes and parse abc string 2579 for t in self.tokens: 2580 # environLocal.printDebug(['tokenProcess: calling parse()', t]) 2581 t.parse() 2582 2583 def process(self, strSrc: str) -> None: 2584 self.tokens = [] 2585 self.tokenize(strSrc) 2586 self.tokenProcess() 2587 # return list of tokens; stored internally 2588 2589 # -------------------------------------------------------------------------- 2590 # access tokens 2591 2592 def __len__(self): 2593 return len(self.tokens) 2594 2595 def __add__(self, other): 2596 ''' 2597 Return a new handler adding the tokens in both 2598 2599 Contrived example appending two separate keys. 2600 2601 Used in polyphonic metadata merge 2602 2603 2604 >>> abcStr = 'M:6/8\\nL:1/8\\nK:G\\n' 2605 >>> ah1 = abcFormat.ABCHandler() 2606 >>> junk = ah1.process(abcStr) 2607 >>> len(ah1) 2608 3 2609 2610 >>> abcStr = 'M:3/4\\nL:1/4\\nK:D\\n' 2611 >>> ah2 = abcFormat.ABCHandler() 2612 >>> junk = ah2.process(abcStr) 2613 >>> len(ah2) 2614 3 2615 2616 >>> ah3 = ah1 + ah2 2617 >>> len(ah3) 2618 6 2619 >>> ah3.tokens[0] == ah1.tokens[0] 2620 True 2621 >>> ah3.tokens[3] == ah2.tokens[0] 2622 True 2623 2624 ''' 2625 ah = self.__class__() # will get the same class type 2626 ah.tokens = self.tokens + other.tokens 2627 return ah 2628 2629 # -------------------------------------------------------------------------- 2630 # utility methods for post processing 2631 2632 def definesReferenceNumbers(self): 2633 ''' 2634 Return True if this token structure defines more than 1 reference number, 2635 usually implying multiple pieces encoded in one file. 2636 2637 2638 >>> abcStr = 'X:5\\nM:6/8\\nL:1/8\\nK:G\\nB3 A3 | G6 | B3 A3 | G6 ||' 2639 >>> ah = abcFormat.ABCHandler() 2640 >>> junk = ah.process(abcStr) 2641 >>> ah.definesReferenceNumbers() # only one returns False 2642 False 2643 2644 2645 >>> abcStr = 'X:5\\nM:6/8\\nL:1/8\\nK:G\\nB3 A3 | G6 | B3 A3 | G6 ||\\n' 2646 >>> abcStr += 'X:6\\nM:6/8\\nL:1/8\\nK:G\\nB3 A3 | G6 | B3 A3 | G6 ||' 2647 >>> ah = abcFormat.ABCHandler() 2648 >>> junk = ah.process(abcStr) 2649 >>> ah.definesReferenceNumbers() # two tokens so returns True 2650 True 2651 ''' 2652 if not self.tokens: 2653 raise ABCHandlerException('must process tokens before calling split') 2654 count = 0 2655 for i in range(len(self.tokens)): 2656 t = self.tokens[i] 2657 if isinstance(t, ABCMetadata): 2658 if t.isReferenceNumber(): 2659 count += 1 2660 if count > 1: 2661 return True 2662 return False 2663 2664 def splitByReferenceNumber(self): 2665 # noinspection PyShadowingNames 2666 r''' 2667 Split tokens by reference numbers. 2668 2669 Returns a dictionary of ABCHandler instances, where the reference number 2670 is used to access the music. If no reference numbers are defined, 2671 the tune is available under the dictionary entry None. 2672 2673 2674 >>> abcStr = 'X:5\nM:6/8\nL:1/8\nK:G\nB3 A3 | G6 | B3 A3 | G6 ||' 2675 >>> abcStr += 'X:6\nM:6/8\nL:1/8\nK:G\nB3 A3 | G6 | B3 A3 | G6 ||' 2676 >>> ah = abcFormat.ABCHandler() 2677 >>> junk = ah.process(abcStr) 2678 >>> len(ah) 2679 28 2680 >>> ahDict = ah.splitByReferenceNumber() 2681 >>> 5 in ahDict 2682 True 2683 >>> 6 in ahDict 2684 True 2685 >>> 7 in ahDict 2686 False 2687 2688 Each entry is its own ABCHandler object. 2689 2690 >>> ahDict[5] 2691 <music21.abcFormat.ABCHandler object at 0x10b0cf5f8> 2692 >>> len(ahDict[5].tokens) 2693 14 2694 2695 Header information (except for comments) should be appended to all pieces. 2696 2697 >>> abcStrWHeader = '%abc-2.1\nO: Irish\n' + abcStr 2698 >>> ah = abcFormat.ABCHandler() 2699 >>> junk = ah.process(abcStrWHeader) 2700 >>> len(ah) 2701 29 2702 >>> ahDict = ah.splitByReferenceNumber() 2703 >>> 5 in ahDict 2704 True 2705 >>> 6 in ahDict 2706 True 2707 >>> 7 in ahDict 2708 False 2709 2710 Did we get the origin header in each score? 2711 2712 >>> ahDict[5].tokens[0] 2713 <music21.abcFormat.ABCMetadata 'O: Irish'> 2714 >>> ahDict[6].tokens[0] 2715 <music21.abcFormat.ABCMetadata 'O: Irish'> 2716 ''' 2717 if not self.tokens: 2718 raise ABCHandlerException('must process tokens before calling split') 2719 2720 ahDict = {} 2721 2722 # tokens in this list are prepended to all tunes: 2723 prependToAllList = [] 2724 activeTokens = [] 2725 currentABCHandler = None 2726 2727 for i, t in enumerate(self.tokens): 2728 if isinstance(t, ABCMetadata) and t.isReferenceNumber(): 2729 if currentABCHandler is not None: 2730 currentABCHandler.tokens = activeTokens 2731 activeTokens = [] 2732 currentABCHandler = ABCHandler() 2733 referenceNumber = int(t.data) 2734 ahDict[referenceNumber] = currentABCHandler 2735 2736 if currentABCHandler is None: 2737 prependToAllList.append(t) 2738 else: 2739 activeTokens.append(t) 2740 2741 if currentABCHandler is not None: 2742 currentABCHandler.tokens = activeTokens 2743 2744 if not ahDict: 2745 ahDict[None] = ABCHandler() 2746 2747 for thisABCHandler in ahDict.values(): 2748 thisABCHandler.tokens = prependToAllList[:] + thisABCHandler.tokens 2749 2750 return ahDict 2751 2752 def getReferenceNumber(self): 2753 ''' 2754 If tokens are processed, get the first 2755 reference number defined. 2756 2757 2758 >>> abcStr = 'X:5\\nM:6/8\\nL:1/8\\nK:G\\nB3 A3 | G6 | B3 A3 | G6 ||' 2759 >>> ah = abcFormat.ABCHandler() 2760 >>> junk = ah.process(abcStr) 2761 >>> ah.getReferenceNumber() 2762 '5' 2763 ''' 2764 if not self.tokens: 2765 raise ABCHandlerException('must process tokens before calling split') 2766 for t in self.tokens: 2767 if isinstance(t, ABCMetadata): 2768 if t.isReferenceNumber(): 2769 return t.data 2770 return None 2771 2772 def definesMeasures(self): 2773 ''' 2774 Returns True if this token structure defines Measures in a normal Measure form. 2775 Otherwise False 2776 2777 >>> abcStr = ('M:6/8\\nL:1/8\\nK:G\\nV:1 name="Whistle" ' + 2778 ... 'snm="wh"\\nB3 A3 | G6 | B3 A3 | G6 ||\\nV:2 name="violin" ' + 2779 ... 'snm="v"\\nBdB AcA | GAG D3 | BdB AcA | GAG D6 ||\\nV:3 name="Bass" ' + 2780 ... 'snm="b" clef=bass\\nD3 D3 | D6 | D3 D3 | D6 ||') 2781 >>> ah = abcFormat.ABCHandler() 2782 >>> junk = ah.process(abcStr) 2783 >>> ah.definesMeasures() 2784 True 2785 2786 >>> abcStr = 'M:6/8\\nL:1/8\\nK:G\\nB3 A3 G6 B3 A3 G6' 2787 >>> ah = abcFormat.ABCHandler() 2788 >>> junk = ah.process(abcStr) 2789 >>> ah.definesMeasures() 2790 False 2791 ''' 2792 if not self.tokens: 2793 raise ABCHandlerException('must process tokens before calling split') 2794 count = 0 2795 for i in range(len(self.tokens)): 2796 t = self.tokens[i] 2797 if isinstance(t, ABCBar): 2798 # must define at least 2 regular barlines 2799 # this leave out cases where only double bars are given 2800 if t.isRegular(): 2801 count += 1 2802 # forcing the inclusion of two measures to count 2803 if count >= 2: 2804 return True 2805 return False 2806 2807 def splitByVoice(self) -> List['ABCHandler']: 2808 # noinspection PyShadowingNames 2809 ''' 2810 Given a processed token list, look for voices. If voices exist, 2811 split into parts: common metadata, then next voice, next voice, etc. 2812 2813 Each part is returned as a ABCHandler instance. 2814 2815 >>> abcStr = ('M:6/8\\nL:1/8\\nK:G\\nV:1 name="Whistle" ' + 2816 ... 'snm="wh"\\nB3 A3 | G6 | B3 A3 | G6 ||\\nV:2 name="violin" ' + 2817 ... 'snm="v"\\nBdB AcA | GAG D3 | BdB AcA | GAG D6 ||\\nV:3 name="Bass" ' + 2818 ... 'snm="b" clef=bass\\nD3 D3 | D6 | D3 D3 | D6 ||') 2819 >>> ah = abcFormat.ABCHandler() 2820 >>> ah.process(abcStr) 2821 >>> tokenColls = ah.splitByVoice() 2822 >>> tokenColls[0] 2823 <music21.abcFormat.ABCHandler object at 0x...> 2824 2825 Common headers are first 2826 2827 >>> [t.src for t in tokenColls[0].tokens] 2828 ['M:6/8', 'L:1/8', 'K:G'] 2829 2830 Then each voice 2831 2832 >>> [t.src for t in tokenColls[1].tokens] 2833 ['V:1 name="Whistle" snm="wh"', 'B3', 'A3', '|', 'G6', '|', 'B3', 'A3', '|', 'G6', '||'] 2834 >>> [t.src for t in tokenColls[2].tokens] 2835 ['V:2 name="violin" snm="v"', 'B', 'd', 'B', 'A', 'c', 'A', '|', 2836 'G', 'A', 'G', 'D3', '|', 'B', 'd', 'B', 'A', 'c', 'A', '|', 'G', 'A', 'G', 'D6', '||'] 2837 >>> [t.src for t in tokenColls[3].tokens] 2838 ['V:3 name="Bass" snm="b" clef=bass', 'D3', 'D3', '|', 'D6', '|', 2839 'D3', 'D3', '|', 'D6', '||'] 2840 2841 Then later the metadata can be merged at the start of each voice... 2842 2843 >>> mergedTokens = tokenColls[0] + tokenColls[1] 2844 >>> mergedTokens 2845 <music21.abcFormat.ABCHandler object at 0x...> 2846 >>> [t.src for t in mergedTokens.tokens] 2847 ['M:6/8', 'L:1/8', 'K:G', 'V:1 name="Whistle" snm="wh"', 2848 'B3', 'A3', '|', 'G6', '|', 'B3', 'A3', '|', 'G6', '||'] 2849 ''' 2850 # TODO: this procedure should also be responsible for 2851 # breaking the passage into voice/lyric pairs 2852 2853 if not self.tokens: 2854 raise ABCHandlerException('must process tokens before calling split') 2855 2856 voiceCount = 0 2857 pos = [] 2858 for i in range(len(self.tokens)): 2859 t = self.tokens[i] 2860 if isinstance(t, ABCMetadata): 2861 if t.isVoice(): 2862 # if first char is a number 2863 # can be V:3 name="Bass" snm="b" clef=bass 2864 if t.data[0].isdigit(): 2865 pos.append(i) # store position 2866 voiceCount += 1 2867 2868 abcHandlers = [] 2869 # no voices, or definition of one voice, or use of V: field for 2870 # something else 2871 if voiceCount <= 1: 2872 ah = self.__class__() # just making a copy 2873 ah.tokens = self.tokens 2874 abcHandlers.append(ah) 2875 # two or more voices 2876 else: 2877 # collect start and end pairs of split 2878 pairs = [] 2879 pairs.append([0, pos[0]]) 2880 i = pos[0] 2881 for x in range(1, len(pos)): 2882 j = pos[x] 2883 pairs.append([i, j]) 2884 i = j 2885 # add last 2886 pairs.append([i, len(self)]) 2887 2888 for x, y in pairs: 2889 ah = self.__class__() 2890 ah.tokens = self.tokens[x:y] 2891 abcHandlers.append(ah) 2892 2893 return abcHandlers 2894 2895 @staticmethod 2896 def _buildMeasureBoundaryIndices( 2897 positionList: List[int], 2898 lastValidIndex: int 2899 ) -> List[List[int]]: 2900 ''' 2901 Staticmethod 2902 2903 Given a list of indices of a list marking the position of 2904 each barline or implied barline, and the last valid index, 2905 return a list of two-element lists, each indicating 2906 the start and positions of a measure. 2907 2908 Here's an easy case that makes this method look worthless: 2909 2910 >>> AH = abcFormat.ABCHandler 2911 >>> AH._buildMeasureBoundaryIndices([8, 12, 16], 20) 2912 [[0, 8], [8, 12], [12, 16], [16, 20]] 2913 2914 But in this case, we need to see that 12 and 13 don't represent different measures but 2915 probably represent an end and new barline (repeat bar), etc. 2916 2917 >>> AH._buildMeasureBoundaryIndices([8, 12, 13, 16], 20) 2918 [[0, 8], [8, 12], [13, 16], [16, 20]] 2919 2920 Here 115 is both the last barline and the last index, so there is no [115, 115] entry. 2921 2922 >>> bi = [9, 10, 16, 23, 29, 36, 42, 49, 56, 61, 62, 64, 70, 77, 84, 90, 96, 103, 110, 115] 2923 >>> AH._buildMeasureBoundaryIndices(bi, 115) 2924 [[0, 9], [10, 16], [16, 23], [23, 29], [29, 36], [36, 42], [42, 49], [49, 56], [56, 61], 2925 [62, 64], [64, 70], [70, 77], [77, 84], [84, 90], [90, 96], 2926 [96, 103], [103, 110], [110, 115]] 2927 2928 ''' 2929 # collect start and end pairs of split 2930 pairs = [] 2931 # first chunk is metadata, as first token is probably not a bar 2932 pairs.append([0, positionList[0]]) 2933 i = positionList[0] # get first bar position stored 2934 # iterate through every other bar position (already have first) 2935 for x in range(1, len(positionList)): 2936 j = positionList[x] 2937 if j == i + 1: # a span of one is skipped 2938 i = j 2939 continue 2940 pairs.append([i, j]) 2941 i = j # the end becomes the new start 2942 # add last valid index 2943 if i != lastValidIndex: 2944 pairs.append([i, lastValidIndex]) 2945 # environLocal.printDebug(['splitByMeasure(); pairs pre filter', pairs]) 2946 return pairs 2947 2948 def splitByMeasure(self) -> List['ABCHandlerBar']: 2949 ''' 2950 Divide a token list by Measures, also 2951 defining start and end bars of each Measure. 2952 2953 If a component does not have notes, leave 2954 as an empty bar. This is often done with leading metadata. 2955 2956 Returns a list of ABCHandlerBar instances. 2957 The first usually defines only Metadata 2958 2959 TODO: Test and examples 2960 ''' 2961 if not self.tokens: 2962 raise ABCHandlerException('must process tokens before calling split') 2963 2964 abcBarHandlers = [] 2965 barIndices = self.tokensToBarIndices() 2966 2967 # barCount = 0 # not used 2968 # noteCount = 0 # not used 2969 2970 # environLocal.printDebug(['splitByMeasure(); raw bar positions', barIndices]) 2971 measureIndices = self._buildMeasureBoundaryIndices(barIndices, len(self) - 1) 2972 # for x, y in pairs: 2973 # environLocal.printDebug(['boundary indices:', x, y]) 2974 # environLocal.printDebug([' values at x, y', self.tokens[x], self.tokens[y]]) 2975 2976 # iterate through start and end pairs 2977 for x, y in measureIndices: 2978 ah = ABCHandlerBar() 2979 # this will get the first to last 2980 # shave of tokens if not needed 2981 xClip = x 2982 yClip = y 2983 2984 # check if first is a bar; if so, assign and remove 2985 if isinstance(self.tokens[x], ABCBar): 2986 lbCandidate = self.tokens[x] 2987 # if we get an end repeat, probably already assigned this 2988 # in the last measure, so skip 2989 # environLocal.printDebug(['reading pairs, got token:', lbCandidate, 2990 # 'lbCandidate.barType', lbCandidate.barType, 2991 # 'lbCandidate.repeatForm', lbCandidate.repeatForm]) 2992 # skip end repeats assigned (improperly) to the left 2993 if (lbCandidate.barType == 'repeat' 2994 and lbCandidate.repeatForm == 'end'): 2995 pass 2996 else: # assign 2997 ah.leftBarToken = lbCandidate 2998 # environLocal.printDebug(['splitByMeasure(); assigning left bar token', 2999 # lbCandidate]) 3000 # always trim if we have a bar 3001 xClip = x + 1 3002 # ah.tokens = ah.tokens[1:] # remove first, as not done above 3003 3004 # if x boundary is metadata, do not include it (as it is likely in the previous 3005 # measure) unless it is at the beginning. 3006 elif x != 0 and isinstance(self.tokens[x], ABCMetadata): 3007 xClip = x + 1 3008 else: 3009 # if we find a note in the x-clip position, it is likely a pickup the 3010 # first note after metadata. this we keep, b/c it 3011 # should be part of this branch 3012 pass 3013 3014 if y >= len(self): 3015 yTestIndex = len(self) 3016 else: 3017 yTestIndex = y 3018 3019 if isinstance(self.tokens[yTestIndex], ABCBar): 3020 rbCandidate = self.tokens[yTestIndex] 3021 # if a start repeat, save it to be placed as a left barline 3022 if not (rbCandidate.barType == 'repeat' 3023 and rbCandidate.repeatForm == 'start'): 3024 # environLocal.printDebug(['splitByMeasure(); assigning right bar token', 3025 # lbCandidate]) 3026 ah.rightBarToken = self.tokens[yTestIndex] 3027 # always trim if we have a bar 3028 # ah.tokens = ah.tokens[:-1] # remove last 3029 yClip = y - 1 3030 # if y boundary is metadata, include it 3031 elif isinstance(self.tokens[yTestIndex], ABCMetadata): 3032 pass # no change 3033 # if y position is a note/chord, and this is the last index, 3034 # must included it 3035 elif not (isinstance(self.tokens[yTestIndex], (ABCNote, ABCChord)) 3036 and yTestIndex == len(self.tokens) - 1): 3037 # if we find a note in the yClip position, it is likely 3038 # a pickup, the first note after metadata. we do not include this 3039 yClip = yTestIndex - 1 3040 3041 # environLocal.printDebug(['clip boundaries: x,y', xClip, yClip]) 3042 # boundaries are inclusive; need to add one here 3043 ah.tokens = self.tokens[xClip:yClip + 1] 3044 # after bar assign, if no bars known, reject 3045 if not ah: 3046 continue 3047 abcBarHandlers.append(ah) 3048 3049 # for sub in abcBarHandlers: 3050 # environLocal.printDebug(['concluded splitByMeasure:', sub, 3051 # 'leftBarToken', sub.leftBarToken, 'rightBarToken', sub.rightBarToken, 3052 # 'len(sub)', len(sub), 'sub.hasNotes()', sub.hasNotes()]) 3053 # for t in sub.tokens: 3054 # print('\t', t) 3055 return abcBarHandlers 3056 3057 def tokensToBarIndices(self) -> List[int]: 3058 ''' 3059 Return a list of indices indicating which tokens in self.tokens are 3060 bar lines or the last piece of metadata before a note or chord. 3061 ''' 3062 barIndices = [] 3063 tNext = None 3064 for i, t in enumerate(self.tokens): 3065 try: 3066 tNext = self.tokens[i + 1] 3067 except IndexError: 3068 tNext = None 3069 3070 # either we get a bar, or we just complete metadata and we 3071 # encounter a note (a pickup) 3072 if isinstance(t, ABCBar): # or (barCount == 0 and noteCount > 0): 3073 # environLocal.printDebug(['splitByMeasure()', 'found bar', t]) 3074 barIndices.append(i) # store position 3075 # barCount += 1 # not used 3076 # case of end of metadata and start of notes in a pickup 3077 # tag the last metadata as the end 3078 elif (isinstance(t, ABCMetadata) 3079 and tNext is not None 3080 and isinstance(tNext, (ABCNote, ABCChord))): 3081 barIndices.append(i) # store position 3082 3083 return barIndices 3084 3085 def hasNotes(self) -> bool: 3086 ''' 3087 If tokens are processed, return True if ABCNote or 3088 ABCChord classes are defined 3089 3090 3091 >>> abcStr = 'M:6/8\\nL:1/8\\nK:G\\n' 3092 >>> ah1 = abcFormat.ABCHandler() 3093 >>> junk = ah1.process(abcStr) 3094 >>> ah1.hasNotes() 3095 False 3096 3097 >>> abcStr = 'M:6/8\\nL:1/8\\nK:G\\nc1D2' 3098 >>> ah2 = abcFormat.ABCHandler() 3099 >>> junk = ah2.process(abcStr) 3100 >>> ah2.hasNotes() 3101 True 3102 ''' 3103 if not self.tokens: 3104 raise ABCHandlerException('must process tokens before calling') 3105 count = 0 3106 for t in self.tokens: 3107 if isinstance(t, (ABCNote, ABCChord)): 3108 count += 1 3109 # environLocal.printDebug(['hasNotes', count]) 3110 if count > 0: 3111 return True 3112 else: 3113 return False 3114 3115 def getTitle(self) -> Optional[str]: 3116 ''' 3117 Get the first title tag. Used for testing. 3118 3119 Requires tokens to have been processed. 3120 ''' 3121 if not self.tokens: 3122 raise ABCHandlerException('must process tokens before calling split') 3123 for t in self.tokens: 3124 if isinstance(t, ABCMetadata): 3125 if t.isTitle(): 3126 return t.data 3127 return None 3128 3129 3130class ABCHandlerBar(ABCHandler): 3131 ''' 3132 A Handler specialized for storing bars. All left 3133 and right bars are collected and assigned to attributes. 3134 ''' 3135 # divide elements of a character stream into objects and handle 3136 # store in a list, and pass global information to components 3137 3138 def __init__(self): 3139 # tokens are ABC objects in a linear stream 3140 super().__init__() 3141 3142 self.leftBarToken = None 3143 self.rightBarToken = None 3144 3145 def __add__(self, other): 3146 ah = self.__class__() # will get the same class type 3147 ah.tokens = self.tokens + other.tokens 3148 # get defined tokens 3149 for barAttr in ('leftBarToken', 'rightBarToken'): 3150 bOld = getattr(self, barAttr) 3151 bNew = getattr(other, barAttr) 3152 if bNew is None and bOld is None: 3153 pass # nothing to do 3154 elif bNew is not None and bOld is None: # get new 3155 setattr(ah, barAttr, bNew) 3156 elif bNew is None and bOld is not None: # get old 3157 setattr(ah, barAttr, bOld) 3158 else: 3159 # if both ar the same, assign one 3160 if bOld.src == bNew.src: 3161 setattr(ah, barAttr, bNew) 3162 else: 3163 # might resolve this by ignoring standard bars and favoring 3164 # repeats or styled bars 3165 environLocal.printDebug(['cannot handle two non-None bars yet: got bNew, bOld', 3166 bNew, bOld]) 3167 # raise ABCHandlerException('cannot handle two non-None bars yet') 3168 setattr(ah, barAttr, bNew) 3169 3170 return ah 3171 3172 3173def mergeLeadingMetaData(barHandlers: List[ABCHandlerBar]) -> List[ABCHandlerBar]: 3174 ''' 3175 Given a list of ABCHandlerBar objects, return a list of ABCHandlerBar 3176 objects where leading metadata is merged, if possible, 3177 with the bar data following. 3178 3179 This consolidates all metadata in bar-like entities. 3180 ''' 3181 mCount = 0 3182 metadataPos = [] # store indices of handlers that are all metadata 3183 for i in range(len(barHandlers)): 3184 if barHandlers[i].hasNotes(): 3185 mCount += 1 3186 else: 3187 metadataPos.append(i) 3188 # environLocal.printDebug(['mergeLeadingMetaData()', 3189 # 'metadataPosList', metadataPos, 'mCount', mCount]) 3190 # merge meta data into bars for processing 3191 mergedHandlers = [] 3192 if mCount <= 1: # if only one true measure, do not create measures 3193 ahb = ABCHandlerBar() 3194 for h in barHandlers: 3195 ahb += h # concatenate all 3196 mergedHandlers.append(ahb) 3197 else: 3198 # when we have metadata, we need to pass its tokens with those 3199 # of the measure that follows it; if we have trailing meta data, 3200 # we can pass but do not create a measure 3201 i = 0 3202 while i < len(barHandlers): 3203 # if we find metadata and it is not the last valid index 3204 # merge into a single handler 3205 if i in metadataPos and i != len(barHandlers) - 1: 3206 mergedHandlers.append(barHandlers[i] + barHandlers[i + 1]) 3207 i += 2 3208 else: 3209 mergedHandlers.append(barHandlers[i]) 3210 i += 1 3211 3212 return mergedHandlers 3213 3214# ------------------------------------------------------------------------------ 3215 3216 3217class ABCFile(prebase.ProtoM21Object): 3218 ''' 3219 ABC File or String access 3220 3221 The abcVersion attribution optionally specifies the (major, minor, patch) 3222 version of ABC to process-- e.g., (1.2.0). 3223 If not set, default ABC 1.3 parsing is performed. 3224 ''' 3225 def __init__(self, abcVersion=None): 3226 self.abcVersion = abcVersion 3227 self.file = None 3228 self.filename = None 3229 3230 def open(self, filename): 3231 ''' 3232 Open a file for reading 3233 ''' 3234 # try: 3235 self.file = io.open(filename, encoding='utf-8') # pylint: disable=consider-using-with 3236 # except 3237 # self.file = io.open(filename, encoding='latin-1') 3238 self.filename = filename 3239 3240 def openFileLike(self, fileLike): 3241 ''' 3242 Assign a file-like object, such as those provided by 3243 StringIO, as an open file object. 3244 3245 >>> from io import StringIO 3246 >>> fileLikeOpen = StringIO() 3247 ''' 3248 self.file = fileLike # already 'open' 3249 3250 def _reprInternal(self): 3251 return '' 3252 3253 def close(self): 3254 self.file.close() 3255 3256 def read(self, number=None): 3257 ''' 3258 Read a file. Note that this calls readstr, 3259 which processes all tokens. 3260 3261 If `number` is given, a work number will be extracted if possible. 3262 ''' 3263 return self.readstr(self.file.read(), number) 3264 3265 @staticmethod 3266 def extractReferenceNumber(strSrc: str, number: int) -> str: 3267 ''' 3268 Extract the string data relating to a single reference number 3269 from a file that defines multiple songs or pieces. 3270 3271 This method permits loading a single work from a collection/opus 3272 without parsing the entire file. 3273 3274 Here is sample data that is not correct ABC but demonstrates the basic concept: 3275 3276 >>> fileData = """ 3277 ... X:1 3278 ... Hello 3279 ... X:2 3280 ... Aloha 3281 ... X:3 3282 ... Goodbye 3283 ... """ 3284 3285 >>> file2 = abcFormat.ABCFile.extractReferenceNumber(fileData, 2) 3286 >>> print(file2) 3287 X:2 3288 Aloha 3289 3290 If the number does not exist, raises an ABCFileException: 3291 3292 >>> abcFormat.ABCFile.extractReferenceNumber(fileData, 99) 3293 Traceback (most recent call last): 3294 music21.abcFormat.ABCFileException: cannot find requested 3295 reference number in source file: 99 3296 3297 3298 If the same number is defined twice in one file (should not be) only 3299 the first data is returned. 3300 3301 Changed in v6.2: now a static method. 3302 ''' 3303 collect = [] 3304 gather = False 3305 for line in strSrc.split('\n'): 3306 # must be a single line definition 3307 # rstrip because of '\r\n' carriage returns 3308 if line.strip().startswith('X:') and line.replace(' ', '').rstrip() == f'X:{number}': 3309 gather = True 3310 elif line.strip().startswith('X:') and not gather: 3311 # some numbers are like X:0490 but we may request them as 490... 3312 try: 3313 forcedNum = int(line.replace(' ', '').rstrip().replace('X:', '')) 3314 if forcedNum == int(number): 3315 gather = True 3316 except TypeError: 3317 pass 3318 # if already gathering and find another ref number definition 3319 # stop gathering 3320 elif gather and line.strip().startswith('X:'): 3321 break 3322 3323 if gather: 3324 collect.append(line) 3325 3326 if not collect: 3327 raise ABCFileException( 3328 f'cannot find requested reference number in source file: {number}') 3329 3330 referenceNumbers = '\n'.join(collect) 3331 return referenceNumbers 3332 3333 def readstr(self, strSrc: str, number: Optional[int] = None) -> ABCHandler: 3334 ''' 3335 Read a string and process all Tokens. 3336 Returns a ABCHandler instance. 3337 ''' 3338 if number is not None: 3339 # will raise exception if cannot be found 3340 strSrc = self.extractReferenceNumber(strSrc, number) 3341 3342 handler = ABCHandler(abcVersion=self.abcVersion) 3343 # return the handler instance 3344 handler.process(strSrc) 3345 return handler 3346 3347 3348# ------------------------------------------------------------------------------ 3349class Test(unittest.TestCase): 3350 3351 def testTokenization(self): 3352 from music21.abcFormat import testFiles 3353 3354 for (tf, countTokens, noteTokens, chordTokens) in [ 3355 (testFiles.fyrareprisarn, 241, 152, 0), 3356 (testFiles.mysteryReel, 192, 153, 0), 3357 (testFiles.aleIsDear, 291, 206, 32), 3358 (testFiles.testPrimitive, 100, 75, 2), 3359 (testFiles.williamAndNancy, 127, 93, 0), 3360 (testFiles.morrisonsJig, 178, 137, 0), 3361 ]: 3362 3363 handler = ABCHandler() 3364 handler.tokenize(tf) 3365 tokens = handler.tokens # get private for testing 3366 self.assertEqual(len(tokens), countTokens) 3367 countNotes = 0 3368 countChords = 0 3369 for o in tokens: 3370 if isinstance(o, ABCChord): 3371 countChords += 1 3372 elif isinstance(o, ABCNote): 3373 countNotes += 1 3374 3375 self.assertEqual(countNotes, noteTokens) 3376 self.assertEqual(countChords, chordTokens) 3377 3378 def testRe(self): 3379 3380 src = 'A: this is a test' 3381 post = reMetadataTag.match(src).end() 3382 self.assertEqual(src[:post], 'A:') 3383 self.assertEqual(src[post:], ' this is a test') 3384 3385 src = 'Q: this is a test % and a following comment' 3386 post = reMetadataTag.match(src).end() 3387 self.assertEqual(src[:post], 'Q:') 3388 3389 # chord symbol matches 3390 src = 'd|"G"e2d B2d|"C"gfe "D7"d2d|"G"e2d B2d|"A7""C"gfe "D7""D"d2c|' 3391 post = reChordSymbol.findall(src) 3392 self.assertEqual(post, ['"G"', '"C"', '"D7"', '"G"', '"A7"', 3393 '"C"', '"D7"', '"D"']) 3394 3395 # get index of last match of many 3396 i = list(reChordSymbol.finditer(src))[-1].end() 3397 3398 src = '=d2' 3399 self.assertEqual(rePitchName.findall(src)[0], 'd') 3400 3401 src = 'A3/2' 3402 self.assertEqual(rePitchName.findall(src)[0], 'A') 3403 3404 def testTokenProcessMetadata(self): 3405 from music21.abcFormat import testFiles 3406 3407 # noinspection SpellCheckingInspection 3408 for (tf, titleEncoded, meterEncoded, keyEncoded) in [ 3409 (testFiles.fyrareprisarn, 'Fyrareprisarn', '3/4', 'F'), 3410 (testFiles.mysteryReel, 'Mystery Reel', 'C|', 'G'), 3411 (testFiles.aleIsDear, 'Ale is Dear, The', '4/4', 'D', ), 3412 (testFiles.kitchGirl, 'Kitchen Girl', '4/4', 'D'), 3413 (testFiles.williamAndNancy, 'William and Nancy', '6/8', 'G'), 3414 ]: 3415 3416 handler = ABCHandler() 3417 handler.tokenize(tf) 3418 handler.tokenProcess() 3419 3420 tokens = handler.tokens # get private for testing 3421 for t in tokens: 3422 if isinstance(t, ABCMetadata): 3423 if t.tag == 'T': 3424 self.assertEqual(t.data, titleEncoded) 3425 elif t.tag == 'M': 3426 self.assertEqual(t.data, meterEncoded) 3427 elif t.tag == 'K': 3428 self.assertEqual(t.data, keyEncoded) 3429 3430 def testTokenProcess(self): 3431 from music21.abcFormat import testFiles 3432 3433 for tf in [ 3434 testFiles.fyrareprisarn, 3435 testFiles.mysteryReel, 3436 testFiles.aleIsDear, 3437 testFiles.testPrimitive, 3438 testFiles.kitchGirl, 3439 testFiles.williamAndNancy, 3440 ]: 3441 3442 handler = ABCHandler() 3443 handler.tokenize(tf) 3444 handler.tokenProcess() 3445 3446 def testNoteParse(self): 3447 from music21 import key 3448 3449 an = ABCNote() 3450 3451 # with a key signature, matching steps are assumed altered 3452 an.activeKeySignature = key.KeySignature(3) 3453 self.assertEqual(an.getPitchName('c'), ('C#5', False)) 3454 3455 an.activeKeySignature = None 3456 self.assertEqual(an.getPitchName('c'), ('C5', None)) 3457 self.assertEqual(an.getPitchName('^c'), ('C#5', True)) 3458 3459 an.activeKeySignature = key.KeySignature(-3) 3460 self.assertEqual(an.getPitchName('B'), ('B-4', False)) 3461 3462 an.activeKeySignature = None 3463 self.assertEqual(an.getPitchName('B'), ('B4', None)) 3464 self.assertEqual(an.getPitchName('_B'), ('B-4', True)) 3465 3466 def testSplitByMeasure(self): 3467 3468 from music21.abcFormat import testFiles 3469 3470 ah = ABCHandler() 3471 ah.process(testFiles.hectorTheHero) 3472 ahm = ah.splitByMeasure() 3473 3474 for i, l, r in [(0, None, None), # meta data 3475 (2, '|:', '|'), 3476 (3, '|', '|'), 3477 (-2, '[1', ':|'), 3478 (-1, '[2', '|'), 3479 ]: 3480 # print('expecting', i, l, r, ahm[i].tokens) 3481 # print('have', ahm[i].leftBarToken, ahm[i].rightBarToken) 3482 # print() 3483 if l is None: 3484 self.assertEqual(ahm[i].leftBarToken, None) 3485 else: 3486 self.assertEqual(ahm[i].leftBarToken.src, l) 3487 3488 if r is None: 3489 self.assertEqual(ahm[i].rightBarToken, None) 3490 else: 3491 self.assertEqual(ahm[i].rightBarToken.src, r) 3492 3493 # for ahSub in ah.splitByMeasure(): 3494 # environLocal.printDebug(['split by measure:', ahSub.tokens]) 3495 # environLocal.printDebug(['leftBar:', ahSub.leftBarToken, 3496 # 'rightBar:', ahSub.rightBarToken, '\n']) 3497 3498 ah = ABCHandler() 3499 ah.process(testFiles.theBeggerBoy) 3500 ahm = ah.splitByMeasure() 3501 3502 for i, l, r in [(0, None, None), # meta data 3503 (1, None, '|'), 3504 (-1, '||', None), # trailing lyric meta data 3505 ]: 3506 # print(i, l, r, ahm[i].tokens) 3507 if l is None: 3508 self.assertEqual(ahm[i].leftBarToken, None) 3509 else: 3510 self.assertEqual(ahm[i].leftBarToken.src, l) 3511 3512 if r is None: 3513 self.assertEqual(ahm[i].rightBarToken, None) 3514 else: 3515 self.assertEqual(ahm[i].rightBarToken.src, r) 3516 3517 # test a simple string with no bars 3518 ah = ABCHandler() 3519 ah.process('M:6/8\nL:1/8\nK:G\nc1D2') 3520 ahm = ah.splitByMeasure() 3521 3522 for i, l, r in [(0, None, None), # meta data 3523 (-1, None, None), # note data, but no bars 3524 ]: 3525 # print(i, l, r, ahm[i].tokens) 3526 if l is None: 3527 self.assertEqual(ahm[i].leftBarToken, None) 3528 else: 3529 self.assertEqual(ahm[i].leftBarToken.src, l) 3530 3531 if r is None: 3532 self.assertEqual(ahm[i].rightBarToken, None) 3533 else: 3534 self.assertEqual(ahm[i].rightBarToken.src, r) 3535 3536 def testMergeLeadingMetaData(self): 3537 from music21.abcFormat import testFiles 3538 3539 # a case of leading and trailing meta data 3540 ah = ABCHandler() 3541 ah.process(testFiles.theBeggerBoy) 3542 ahm = ah.splitByMeasure() 3543 3544 self.assertEqual(len(ahm), 14) 3545 3546 mergedHandlers = mergeLeadingMetaData(ahm) 3547 3548 # after merging, one less handler as leading meta data is merged 3549 self.assertEqual(len(mergedHandlers), 13) 3550 # the last handler is all trailing metadata 3551 self.assertTrue(mergedHandlers[0].hasNotes()) 3552 self.assertFalse(mergedHandlers[-1].hasNotes()) 3553 self.assertTrue(mergedHandlers[-2].hasNotes()) 3554 # these are all ABCHandlerBar instances with bars defined 3555 self.assertEqual(mergedHandlers[-2].rightBarToken.src, '||') 3556 3557 # a case of only leading meta data 3558 ah = ABCHandler() 3559 ah.process(testFiles.theAleWifesDaughter) 3560 ahm = ah.splitByMeasure() 3561 3562 self.assertEqual(len(ahm), 10) 3563 3564 mergedHandlers = mergeLeadingMetaData(ahm) 3565 # after merging, one less handler as leading meta data is merged 3566 self.assertEqual(len(mergedHandlers), 10) 3567 # all handlers have notes 3568 self.assertTrue(mergedHandlers[0].hasNotes()) 3569 self.assertTrue(mergedHandlers[-1].hasNotes()) 3570 self.assertTrue(mergedHandlers[-2].hasNotes()) 3571 # these are all ABCHandlerBar instances with bars defined 3572 self.assertEqual(mergedHandlers[-1].rightBarToken.src, '|]') 3573 3574 # test a simple string with no bars 3575 ah = ABCHandler() 3576 ah.process('M:6/8\nL:1/8\nK:G\nc1D2') 3577 ahm = ah.splitByMeasure() 3578 3579 # split by measure divides meta data 3580 self.assertEqual(len(ahm), 2) 3581 mergedHandlers = mergeLeadingMetaData(ahm) 3582 # after merging, meta data is merged back 3583 self.assertEqual(len(mergedHandlers), 1) 3584 # and it has notes 3585 self.assertTrue(mergedHandlers[0].hasNotes()) 3586 3587 def testSplitByReferenceNumber(self): 3588 from music21.abcFormat import testFiles 3589 3590 # a case of leading and trailing meta data 3591 ah = ABCHandler() 3592 ah.process(testFiles.theBeggerBoy) 3593 ahs = ah.splitByReferenceNumber() 3594 self.assertEqual(len(ahs), 1) 3595 self.assertEqual(list(ahs.keys()), [5]) 3596 self.assertEqual(len(ahs[5]), 88) # tokens 3597 self.assertEqual(ahs[5].tokens[0].src, 'X:5') # first is retained 3598 # noinspection SpellCheckingInspection 3599 self.assertEqual(ahs[5].getTitle(), 'The Begger Boy') # tokens 3600 3601 ah = ABCHandler() 3602 ah.process(testFiles.testPrimitivePolyphonic) # has no reference num 3603 self.assertEqual(len(ah), 47) # tokens 3604 3605 ahs = ah.splitByReferenceNumber() 3606 self.assertEqual(len(ahs), 1) 3607 self.assertEqual(list(ahs.keys()), [None]) 3608 self.assertEqual(ahs[None].tokens[0].src, 'M:6/8') # first is retained 3609 self.assertEqual(len(ahs[None]), 47) # tokens 3610 3611 ah = ABCHandler() 3612 ah.process(testFiles.valentineJigg) # has no reference num 3613 self.assertEqual(len(ah), 244) # total tokens 3614 3615 ahs = ah.splitByReferenceNumber() 3616 self.assertEqual(len(ahs), 3) 3617 self.assertEqual(sorted(list(ahs.keys())), [166, 167, 168]) 3618 3619 self.assertEqual(ahs[168].tokens[0].src, 'X:168') # first is retained 3620 self.assertEqual(ahs[168].getTitle(), '168 The Castle Gate (HJ)') 3621 self.assertEqual(len(ahs[168]), 89) # tokens 3622 3623 self.assertEqual(ahs[166].tokens[0].src, 'X:166') # first is retained 3624 # noinspection SpellCheckingInspection 3625 self.assertEqual(ahs[166].getTitle(), '166 Valentine Jigg (Pe)') 3626 self.assertEqual(len(ahs[166]), 67) # tokens 3627 3628 self.assertEqual(ahs[167].tokens[0].src, 'X:167') # first is retained 3629 self.assertEqual(ahs[167].getTitle(), '167 The Dublin Jig (HJ)') 3630 self.assertEqual(len(ahs[167]), 88) # tokens 3631 3632 def testExtractReferenceNumber(self): 3633 from music21 import corpus 3634 fp = corpus.getWork('essenFolksong/test0') 3635 3636 af = ABCFile() 3637 af.open(fp) 3638 ah = af.read(5) # returns a parsed handler 3639 af.close() 3640 self.assertEqual(len(ah), 74) 3641 3642 af = ABCFile() 3643 af.open(fp) 3644 ah = af.read(7) # returns a parsed handler 3645 af.close() 3646 self.assertEqual(len(ah), 84) 3647 3648 fp = corpus.getWork('essenFolksong/han1') 3649 af = ABCFile() 3650 af.open(fp) 3651 ah = af.read(339) # returns a parsed handler 3652 af.close() 3653 self.assertEqual(len(ah), 101) 3654 3655 def testSlurs(self): 3656 from music21.abcFormat import testFiles 3657 ah = ABCHandler() 3658 ah.process(testFiles.slurTest) 3659 self.assertEqual(len(ah), 70) # number of tokens 3660 3661 def testTies(self): 3662 from music21.abcFormat import testFiles 3663 ah = ABCHandler() 3664 ah.process(testFiles.tieTest) 3665 self.assertEqual(len(ah), 73) # number of tokens 3666 3667 def testCresc(self): 3668 from music21.abcFormat import testFiles 3669 ah = ABCHandler() 3670 ah.process(testFiles.crescTest) 3671 self.assertEqual(len(ah), 75) 3672 tokens = ah.tokens 3673 i = 0 3674 for t in tokens: 3675 if isinstance(t, ABCCrescStart): 3676 i += 1 3677 self.assertEqual(i, 1) 3678 3679 def testDim(self): 3680 from music21.abcFormat import testFiles 3681 ah = ABCHandler() 3682 ah.process(testFiles.dimTest) 3683 self.assertEqual(len(ah), 75) 3684 tokens = ah.tokens 3685 i = 0 3686 for t in tokens: 3687 if isinstance(t, ABCDimStart): 3688 i += 1 3689 self.assertEqual(i, 1) 3690 3691 def testStaccato(self): 3692 from music21.abcFormat import testFiles 3693 ah = ABCHandler() 3694 ah.process(testFiles.staccTest) 3695 self.assertEqual(len(ah), 80) 3696 3697 def testBow(self): 3698 from music21.abcFormat import testFiles 3699 ah = ABCHandler() 3700 ah.process(testFiles.bowTest) 3701 self.assertEqual(len(ah), 83) 3702 tokens = ah.tokens 3703 i = 0 3704 j = 0 3705 for t in tokens: 3706 if isinstance(t, ABCUpbow): 3707 i += 1 3708 elif isinstance(t, ABCDownbow): 3709 j += 1 3710 self.assertEqual(i, 2) 3711 self.assertEqual(j, 1) 3712 3713 def testAcc(self): 3714 from music21.abcFormat import testFiles 3715 from music21 import abcFormat 3716 ah = abcFormat.ABCHandler() 3717 ah.process(testFiles.accTest) 3718 # noinspection SpellCheckingInspection 3719 tokensCorrect = '''<music21.abcFormat.ABCMetadata 'X: 979'> 3720<music21.abcFormat.ABCMetadata 'T: Staccato test, plus accents and tenuto marks'> 3721<music21.abcFormat.ABCMetadata 'M: 2/4'> 3722<music21.abcFormat.ABCMetadata 'L: 1/16'> 3723<music21.abcFormat.ABCMetadata 'K: Edor'> 3724<music21.abcFormat.ABCNote 'B,2'> 3725<music21.abcFormat.ABCBar '|'> 3726<music21.abcFormat.ABCDimStart '!'> 3727<music21.abcFormat.ABCStaccato '.'> 3728<music21.abcFormat.ABCNote 'E'> 3729<music21.abcFormat.ABCNote '^D'> 3730<music21.abcFormat.ABCStaccato '.'> 3731<music21.abcFormat.ABCNote 'E'> 3732<music21.abcFormat.ABCTie '-'> 3733<music21.abcFormat.ABCNote 'E'> 3734<music21.abcFormat.ABCParenStop '!'> 3735<music21.abcFormat.ABCSlurStart '('> 3736<music21.abcFormat.ABCTuplet '(3'> 3737<music21.abcFormat.ABCStaccato '.'> 3738<music21.abcFormat.ABCNote 'G'> 3739<music21.abcFormat.ABCStaccato '.'> 3740<music21.abcFormat.ABCNote 'F'> 3741<music21.abcFormat.ABCStaccato '.'> 3742<music21.abcFormat.ABCAccent 'K'> 3743<music21.abcFormat.ABCNote 'G'> 3744<music21.abcFormat.ABCParenStop ')'> 3745<music21.abcFormat.ABCNote 'B'> 3746<music21.abcFormat.ABCNote 'A'> 3747<music21.abcFormat.ABCParenStop ')'> 3748<music21.abcFormat.ABCBar '|'> 3749<music21.abcFormat.ABCNote 'E'> 3750<music21.abcFormat.ABCNote '^D'> 3751<music21.abcFormat.ABCTenuto 'M'> 3752<music21.abcFormat.ABCNote 'E'> 3753<music21.abcFormat.ABCNote 'F'> 3754<music21.abcFormat.ABCTuplet '(3'> 3755<music21.abcFormat.ABCSlurStart '('> 3756<music21.abcFormat.ABCNote 'G'> 3757<music21.abcFormat.ABCTie '-'> 3758<music21.abcFormat.ABCNote 'G'> 3759<music21.abcFormat.ABCNote 'G'> 3760<music21.abcFormat.ABCParenStop ')'> 3761<music21.abcFormat.ABCParenStop ')'> 3762<music21.abcFormat.ABCNote 'B'> 3763<music21.abcFormat.ABCStraccent 'k'> 3764<music21.abcFormat.ABCTenuto 'M'> 3765<music21.abcFormat.ABCNote 'A'> 3766<music21.abcFormat.ABCBar '|'> 3767<music21.abcFormat.ABCSlurStart '('> 3768<music21.abcFormat.ABCNote 'E'> 3769<music21.abcFormat.ABCSlurStart '('> 3770<music21.abcFormat.ABCNote '^D'> 3771<music21.abcFormat.ABCNote 'E'> 3772<music21.abcFormat.ABCParenStop ')'> 3773<music21.abcFormat.ABCNote 'F'> 3774<music21.abcFormat.ABCParenStop ')'> 3775<music21.abcFormat.ABCTuplet '(3'> 3776<music21.abcFormat.ABCSlurStart '('> 3777<music21.abcFormat.ABCStraccent 'k'> 3778<music21.abcFormat.ABCNote 'G'> 3779<music21.abcFormat.ABCAccent 'K'> 3780<music21.abcFormat.ABCNote 'F'> 3781<music21.abcFormat.ABCParenStop ')'> 3782<music21.abcFormat.ABCNote 'G'> 3783<music21.abcFormat.ABCParenStop ')'> 3784<music21.abcFormat.ABCNote 'A'> 3785<music21.abcFormat.ABCTie '-'> 3786<music21.abcFormat.ABCNote 'A'> 3787<music21.abcFormat.ABCBar '|'> 3788<music21.abcFormat.ABCSlurStart '('> 3789<music21.abcFormat.ABCNote 'E'> 3790<music21.abcFormat.ABCNote '^D'> 3791<music21.abcFormat.ABCNote 'E'> 3792<music21.abcFormat.ABCNote 'F'> 3793<music21.abcFormat.ABCTuplet '(3'> 3794<music21.abcFormat.ABCSlurStart '('> 3795<music21.abcFormat.ABCNote 'G'> 3796<music21.abcFormat.ABCNote 'F'> 3797<music21.abcFormat.ABCNote 'G'> 3798<music21.abcFormat.ABCParenStop ')'> 3799<music21.abcFormat.ABCParenStop ')'> 3800<music21.abcFormat.ABCParenStop ')'> 3801<music21.abcFormat.ABCNote 'B'> 3802<music21.abcFormat.ABCNote 'A'> 3803<music21.abcFormat.ABCBar '|'> 3804<music21.abcFormat.ABCNote 'G6'> 3805'''.splitlines() 3806 tokensReceived = [str(x) for x in ah.tokens] 3807 self.assertEqual(tokensCorrect, tokensReceived) 3808 3809 self.assertEqual(len(ah), 86) 3810 tokens = ah.tokens 3811 i = 0 3812 j = 0 3813 k = 0 3814 for t in tokens: 3815 if isinstance(t, abcFormat.ABCAccent): 3816 i += 1 3817 elif isinstance(t, abcFormat.ABCStraccent): 3818 j += 1 3819 elif isinstance(t, abcFormat.ABCTenuto): 3820 k += 1 3821 self.assertEqual(i, 2) 3822 self.assertEqual(j, 2) 3823 self.assertEqual(k, 2) 3824 3825 def testGrace(self): 3826 from music21.abcFormat import testFiles 3827 ah = ABCHandler() 3828 ah.process(testFiles.graceTest) 3829 self.assertEqual(len(ah), 85) 3830 3831 def testGuineaPig(self): 3832 from music21.abcFormat import testFiles 3833 ah = ABCHandler() 3834 ah.process(testFiles.guineapigTest) 3835 self.assertEqual(len(ah), 105) 3836 3837 3838# ------------------------------------------------------------------------------ 3839# define presented order in documentation 3840_DOC_ORDER = [ABCFile, ABCHandler, ABCHandlerBar] 3841 3842 3843if __name__ == '__main__': 3844 # sys.arg test options will be used in mainTest() 3845 import music21 3846 music21.mainTest(Test) 3847