1# -*- coding: utf-8 -*- 2# ------------------------------------------------------------------------------ 3# Name: segment.py 4# Purpose: Division of stream.Part into segments for individual handling 5# Authors: Jose Cabal-Ugaz 6# 7# Copyright: Copyright © 2012 Michael Scott Cuthbert and the music21 Project 8# License: BSD, see license.txt 9# ------------------------------------------------------------------------------ 10''' 11Inner classes and functions for transcribing musical segments into braille. 12 13This module was made in consultation with the manual "Introduction to Braille 14Music Transcription, Second Edition" by Mary Turner De Garmo, 2005. It is 15available from the Library of Congress 16`here <https://www.loc.gov/nls/braille-audio-reading-materials/music-materials/>`_, 17and will henceforth be referred to as BMTM. 18''' 19import collections 20import copy 21import enum 22import unittest 23 24from typing import Optional 25 26from music21 import bar 27from music21 import chord 28from music21 import clef 29from music21 import dynamics 30from music21 import exceptions21 31from music21 import environment 32from music21 import expressions 33from music21 import key 34from music21 import layout 35from music21 import meter 36from music21 import note 37from music21 import spanner 38from music21 import stream 39from music21 import tempo 40from music21.prebase import ProtoM21Object 41 42from music21.braille import basic 43from music21.braille import lookup 44from music21.braille import noteGrouping as ngMod 45from music21.braille import text 46from music21.braille.objects import BrailleTranscriptionHelper 47 48from music21.common.numberTools import opFrac 49 50symbols = lookup.symbols 51environRules = environment.Environment('segment.py') 52 53 54# ------------------------------------------------------------------------------ 55class BrailleSegmentException(exceptions21.Music21Exception): 56 pass 57 58 59class Affinity(enum.IntEnum): 60 _LOWEST = -1 61 SIGNATURE = 3 62 TTEXT = 4 63 MMARK = 5 64 LONG_TEXTEXPR = 6 65 INACCORD = 7 66 67 SPLIT1_NOTEGROUP = 8 68 NOTEGROUP = 9 69 SPLIT2_NOTEGROUP = 10 70 71 72# Class Sort Order -- differs for Braille than for general music21 73CSO_NOTE = 10 74CSO_REST = 10 75CSO_CHORD = 10 76CSO_DYNAMIC = 9 77CSO_CLEF = 7 78CSO_BARLINE = 0 79CSO_KEYSIG = 1 80CSO_TIMESIG = 2 81CSO_TTEXT = 3 82CSO_MMARK = 4 83CSO_VOICE = 10 84 85# (music21Object, affinity code, class sort order) 86affinityCodes = [(note.Note, Affinity.NOTEGROUP, CSO_NOTE), 87 (note.Rest, Affinity.NOTEGROUP, CSO_REST), 88 (chord.Chord, Affinity.NOTEGROUP, CSO_CHORD), 89 (dynamics.Dynamic, Affinity.NOTEGROUP, CSO_DYNAMIC), 90 (clef.Clef, Affinity.NOTEGROUP, CSO_CLEF), 91 (bar.Barline, Affinity.SPLIT2_NOTEGROUP, CSO_BARLINE), 92 (key.KeySignature, Affinity.SIGNATURE, CSO_KEYSIG), 93 (meter.TimeSignature, Affinity.SIGNATURE, CSO_TIMESIG), 94 (tempo.TempoText, Affinity.TTEXT, CSO_TTEXT), 95 (tempo.MetronomeMark, Affinity.MMARK, CSO_MMARK), 96 (stream.Voice, Affinity.INACCORD, CSO_VOICE)] 97 98affinityNames = {Affinity.SIGNATURE: 'Signature Grouping', 99 Affinity.TTEXT: 'Tempo Text Grouping', 100 Affinity.MMARK: 'Metronome Mark Grouping', 101 Affinity.LONG_TEXTEXPR: 'Long Text Expression Grouping', 102 Affinity.INACCORD: 'Inaccord Grouping', 103 Affinity.NOTEGROUP: 'Note Grouping', 104 Affinity.SPLIT1_NOTEGROUP: 'Split Note Grouping A', 105 Affinity.SPLIT2_NOTEGROUP: 'Split Note Grouping B', 106 } 107 108excludeFromBrailleElements = [spanner.Slur, 109 layout.SystemLayout, 110 layout.PageLayout, 111 layout.StaffLayout] 112 113# Uncomment when Python 3.8 is the minimum version 114# from typing import TypedDict, Optional 115# class GroupingGlobals(TypedDict): 116# keySignature: Optional[key.KeySignature] 117# timeSignature: Optional[meter.TimeSignature] 118# GROUPING_GLOBALS: GroupingGlobals = {...} 119 120 121GROUPING_GLOBALS = { 122 'keySignature': None, # will be key.KeySignature(0) on first call 123 'timeSignature': None, # will be meter.TimeSignature('4/4') on first call 124} 125 126 127def setGroupingGlobals(): 128 ''' 129 sets defaults for grouping globals. Called first time anything 130 in Braille is run, but saves creating two expensive objects if never run 131 ''' 132 if GROUPING_GLOBALS['keySignature'] is None: 133 # remove noinspection when Python 3.8 is the minimum 134 # noinspection PyTypeChecker 135 GROUPING_GLOBALS['keySignature'] = key.KeySignature(0) 136 if GROUPING_GLOBALS['timeSignature'] is None: 137 # remove noinspection when Python 3.8 is the minimum 138 # noinspection PyTypeChecker 139 GROUPING_GLOBALS['timeSignature'] = meter.TimeSignature('4/4') 140 141 142SEGMENT_MAXNOTESFORSHORTSLUR = 4 143 144MAX_ELEMENTS_IN_SEGMENT = 48 # 8 measures of 6 notes, etc. each 145 146_ThreeDigitNumber = collections.namedtuple('_ThreeDigitNumber', 'hundreds tens ones') 147 148SegmentKey = collections.namedtuple('SegmentKey', 'measure ordinal affinity hand') 149SegmentKey.__new__.__defaults__ = (0, 0, None, None) 150 151 152# ------------------------------------------------------------------------------ 153 154class BrailleElementGrouping(ProtoM21Object): 155 _DOC_ATTR = { 156 'keySignature': 'The last :class:`~music21.key.KeySignature` preceding the grouping.', 157 'timeSignature': 'The last :class:`~music21.meter.TimeSignature` preceding the grouping.', 158 'descendingChords': '''True if a :class:`~music21.chord.Chord` should be spelled 159 from highest to lowest pitch 160 in braille, False if the opposite is the case.''', 161 'showClefSigns': '''If True, clef signs are shown in braille. 162 Representation of music in braille is not 163 dependent upon clefs and staves, so the clef signs would be displayed 164 for referential or historical purposes.''', 165 # 'upperFirstInNoteFingering' : 'No documentation.', 166 'withHyphen': 'If True, this grouping will end with a music hyphen.', 167 'numRepeats': 'The number of times this grouping is repeated.' 168 } 169 def __init__(self, *args): 170 ''' 171 A BrailleElementGrouping is a superclass of list of objects which should be displayed 172 without a space in braille. 173 174 >>> from music21.braille import segment 175 >>> bg = segment.BrailleElementGrouping() 176 >>> bg.append(note.Note('C4')) 177 >>> bg.append(note.Note('D4')) 178 >>> bg.append(note.Rest()) 179 >>> bg.append(note.Note('F4')) 180 >>> bg 181 <music21.braille.segment.BrailleElementGrouping [<music21.note.Note C>, 182 <music21.note.Note D>, <music21.note.Rest quarter>, <music21.note.Note F>]> 183 >>> print(bg) 184 <music21.note.Note C> 185 <music21.note.Note D> 186 <music21.note.Rest quarter> 187 <music21.note.Note F> 188 189 These are the defaults and they are shared across all objects... 190 191 >>> bg.keySignature 192 <music21.key.KeySignature of no sharps or flats> 193 >>> bg.timeSignature 194 <music21.meter.TimeSignature 4/4> 195 196 >>> bg.descendingChords 197 True 198 199 >>> bg.showClefSigns 200 False 201 202 >>> bg.upperFirstInNoteFingering 203 True 204 205 >>> bg.withHyphen 206 False 207 208 >>> bg.numRepeats 209 0 210 ''' 211 super().__init__() 212 self.internalList = list(*args) 213 setGroupingGlobals() 214 215 self.keySignature = GROUPING_GLOBALS['keySignature'] 216 self.timeSignature = GROUPING_GLOBALS['timeSignature'] 217 self.descendingChords = True 218 self.showClefSigns = False 219 self.upperFirstInNoteFingering = True 220 self.withHyphen = False 221 self.numRepeats = 0 222 223 def __getitem__(self, item): 224 return self.internalList[item] 225 226 def __setitem__(self, pos, item): 227 self.internalList[pos] = item 228 229 def __len__(self): 230 return len(self.internalList) 231 232 def __getattr__(self, attr): 233 if attr == 'internalList': 234 raise AttributeError('internalList not defined yet') 235 return getattr(self.internalList, attr) 236 237 def __str__(self): 238 ''' 239 Return an unicode braille representation 240 of each object in the BrailleElementGrouping. 241 ''' 242 allObjects = [] 243 for obj in self: 244 if isinstance(obj, stream.Voice): 245 for obj2 in obj: 246 try: 247 allObjects.append('\n'.join(obj2.editorial.brailleEnglish)) 248 except (AttributeError, TypeError): 249 allObjects.append(str(obj2)) 250 else: 251 try: 252 allObjects.append('\n'.join(obj.editorial.brailleEnglish)) 253 except (AttributeError, TypeError): 254 allObjects.append(str(obj)) 255 if self.numRepeats > 0: 256 allObjects.append(f'** Grouping x {self.numRepeats + 1} **') 257 if self.withHyphen is True: 258 allObjects.append(f'music hyphen {lookup.symbols["music_hyphen"]}') 259 out = '\n'.join(allObjects) 260 return out 261 262 def _reprInternal(self): 263 return repr(self.internalList) 264 265 266class BrailleSegment(text.BrailleText): 267 _DOC_ATTR = { 268 'cancelOutgoingKeySig': '''If True, the previous key signature should be 269 cancelled immediately before a new key signature is encountered.''', 270 'dummyRestLength': '''For a given positive integer n, adds n "dummy rests" 271 near the beginning of a segment. Designed for test purposes, as they 272 are used to demonstrate measure division at the end of braille lines.''', 273 'lineLength': '''The maximum amount of braille characters that should be 274 present in a line. The standard is 40 characters.''', 275 'showFirstMeasureNumber': '''If True, then a measure number is shown 276 following the heading (if applicable) and preceding the music.''', 277 'showHand': '''If set to "right" or "left", shows the corresponding 278 hand sign at the beginning of the first line.''', 279 'showHeading': '''If True, then a braille heading is displayed. 280 See :meth:`~music21.braille.basic.transcribeHeading` 281 for more details on headings.''', 282 'suppressOctaveMarks': '''If True, then all octave marks are suppressed. 283 Designed for test purposes, as octave marks were not presented 284 until Chapter 7 of BMTM.''', 285 'endHyphen': '''If True, then the last 286 :class:`~music21.braille.segment.BrailleElementGrouping` of this 287 segment will be followed by a music hyphen. 288 The last grouping is incomplete, because a segment 289 break occurred in the middle of a measure.''', 290 'beginsMidMeasure': '''If True, then the initial measure number of this 291 segment should be followed by a dot. This segment 292 is starting in the middle of a measure.''' 293 } 294 295 def __init__(self, lineLength: int = 40): 296 ''' 297 A segment is "a group of measures occupying more than one braille line." 298 Music is divided into segments so as to "present the music to the reader 299 in a meaningful manner and to give him convenient reference points to 300 use in memorization" (BMTM, 71). 301 302 >>> brailleSeg = braille.segment.BrailleSegment() 303 304 >>> brailleSeg.cancelOutgoingKeySig 305 True 306 >>> brailleSeg.dummyRestLength 307 308 >>> brailleSeg.lineLength 309 40 310 311 >>> brailleSeg.showFirstMeasureNumber 312 True 313 314 315 Possible showHand values are None, 'right', 'left': 316 317 >>> brailleSeg.showHand is None 318 True 319 320 >>> brailleSeg.showHeading 321 True 322 323 >>> brailleSeg.suppressOctaveMarks 324 False 325 326 >>> brailleSeg.endHyphen 327 False 328 329 >>> brailleSeg.beginsMidMeasure 330 False 331 332 333 A BrailleSegment is a type of defaultdict that returns a BrailleElementGrouping 334 when a key is missing. 335 336 >>> len(brailleSeg.keys()) 337 0 338 >>> beg = brailleSeg[braille.segment.SegmentKey(4, 1, 9)] 339 >>> type(beg) is braille.segment.BrailleElementGrouping 340 True 341 342 343 Of course, creating random keys like this will have consequences: 344 345 >>> print(str(brailleSeg)) 346 ---begin segment--- 347 <music21.braille.segment BrailleSegment> 348 Measure 4, Note Grouping 2: 349 <BLANKLINE> 350 === 351 ---end segment--- 352 ''' 353 super().__init__(lineLength=lineLength) 354 self._groupingDict = {} 355 356 self.groupingKeysToProcess = None 357 self.currentGroupingKey = None 358 self.lastNote = None 359 self.previousGroupingKey = None 360 361 self.showClefSigns: bool = False 362 self.upperFirstInNoteFingering: bool = True 363 self.descendingChords: bool = True 364 365 self.cancelOutgoingKeySig = True 366 self.dummyRestLength = None 367 self.showFirstMeasureNumber = True 368 self.showHand = None # override with None, 'left', or 'right' 369 self.showHeading = True 370 self.suppressOctaveMarks = False 371 self.endHyphen = False 372 self.beginsMidMeasure = False 373 374 def __setitem__(self, item, value): 375 self._groupingDict[item] = value 376 377 def __getitem__(self, item): 378 if item not in self._groupingDict: 379 self._groupingDict[item] = BrailleElementGrouping() 380 return self._groupingDict[item] 381 382 def __delitem__(self, item): 383 if item not in self.__dict__: 384 del self._groupingDict[item] 385 else: 386 return ValueError(f'No item {item!r} in Segment') 387 388 def __getattr__(self, item): 389 return getattr(self._groupingDict, item) 390 391 def __contains__(self, item): 392 return item in self._groupingDict 393 394 def __iter__(self): 395 return iter(self._groupingDict) 396 397 def __len__(self): 398 return len(self._groupingDict) 399 400 @property 401 def brailleText(self): 402 ''' 403 Returns the string from the BrailleText object 404 ''' 405 return text.BrailleText.__str__(self) 406 407 def __str__(self): 408 name = '<music21.braille.segment BrailleSegment>' 409 410 allItems = sorted(self.items()) 411 allKeys = [] 412 allGroupings = [] 413 # noinspection PyArgumentList 414 prevKey = SegmentKey() # defaults are defined. 415 416 for (itemKey, grouping) in allItems: 417 try: 418 if prevKey.affinity == Affinity.SPLIT1_NOTEGROUP: 419 prevKey = itemKey 420 continue 421 except TypeError: 422 pass 423 allKeys.append('Measure {0}, {1} {2}:\n'.format(itemKey.measure, 424 affinityNames[itemKey.affinity], 425 itemKey.ordinal + 1)) 426 gStr = str(grouping) 427 allGroupings.append(gStr) 428 prevKey = itemKey 429 allElementGroupings = '\n'.join([''.join([k, g, '\n===']) 430 for (k, g) in list(zip(allKeys, allGroupings))]) 431 out = '\n'.join(['---begin segment---', 432 name, 433 allElementGroupings, 434 '---end segment---']) 435 return out 436 437 def transcribe(self): 438 ''' 439 transcribes all of the noteGroupings in this dict by: 440 441 first transcribing the Heading (if applicable) 442 then the Measure Number 443 then adds appropriate numbers of dummyRests 444 then adds the Rest of the Note Groupings 445 446 returns brailleText 447 ''' 448 # noinspection PyAttributeOutsideInit 449 self.groupingKeysToProcess = list(sorted(self.keys())) 450 451 if self.showHeading: 452 self.extractHeading() # Heading 453 454 if self.showFirstMeasureNumber: 455 self.extractMeasureNumber() # Measure Number 456 457 if self.dummyRestLength is not None: 458 self.addDummyRests() # Dummy Rests 459 460 self.previousGroupingKey = None 461 while self.groupingKeysToProcess: 462 # noinspection PyAttributeOutsideInit 463 self.currentGroupingKey = self.groupingKeysToProcess.pop(0) 464 465 cgkAffinityGroup = self.currentGroupingKey.affinity 466 467 if cgkAffinityGroup == Affinity.NOTEGROUP: 468 self.extractNoteGrouping() # Note Grouping 469 elif cgkAffinityGroup == Affinity.SIGNATURE: 470 self.extractSignatureGrouping() # Signature(s) Grouping 471 elif cgkAffinityGroup == Affinity.LONG_TEXTEXPR: 472 self.extractLongExpressionGrouping() # Long Expression(s) Grouping 473 # elif cgkAffinityGroup == Affinity.INACCORD: 474 # self.extractInaccordGrouping() # In Accord Grouping 475 elif cgkAffinityGroup == Affinity.TTEXT: 476 self.extractTempoTextGrouping() # Tempo Text Grouping 477 # noinspection PyAttributeOutsideInit 478 self.previousGroupingKey = self.currentGroupingKey 479 480 return self.brailleText 481 482 def addDummyRests(self): 483 ''' 484 Adds as many dummy rests as self.dummyRestLength to the signatures of 485 brailleText 486 487 >>> seg = braille.segment.BrailleSegment() 488 >>> seg.dummyRestLength = 4 489 490 >>> print(braille.lookup.rests['dummy']) 491 ⠄ 492 >>> seg.addDummyRests() 493 >>> print(seg.brailleText) 494 ⠄⠄⠄⠄ 495 ''' 496 dummyRests = [self.dummyRestLength * lookup.rests['dummy']] 497 self.addSignatures(''.join(dummyRests)) 498 499 def extractMeasureNumber(self): 500 ''' 501 Adds a measure number from the segmentKey needing processing 502 503 >>> segKey = braille.segment.SegmentKey(measure=4, ordinal=1, affinity=9) 504 >>> seg = braille.segment.BrailleSegment() 505 506 Initialize a new Key 507 508 >>> type(seg[segKey]) 509 <class 'music21.braille.segment.BrailleElementGrouping'> 510 >>> seg.extractMeasureNumber() 511 >>> print(seg.brailleText) 512 ⠼⠙ 513 514 Add a dot to the measure number if the segment begins mid-measure 515 516 >>> seg = braille.segment.BrailleSegment() 517 >>> seg[segKey] 518 <music21.braille.segment.BrailleElementGrouping []> 519 520 >>> seg.beginsMidMeasure = True 521 >>> seg.extractMeasureNumber() 522 >>> print(seg.brailleText) 523 ⠼⠙⠄ 524 ''' 525 gkp = self.groupingKeysToProcess or sorted(self.keys()) 526 firstSegmentKey = gkp[0] 527 initMeasureNumber = firstSegmentKey.measure 528 brailleNumber = basic.numberToBraille(initMeasureNumber) 529 if self.beginsMidMeasure: 530 brailleNumber += symbols['dot'] 531 532 self.addMeasureNumber(brailleNumber) 533 534 def extractHeading(self): 535 ''' 536 Extract a :class:`~music21.key.KeySignature`, :class:`~music21.meter.TimeSignature, 537 :class:`~music21.tempo.TempoText` and :class:`~music21.tempo.MetronomeMark` and 538 add an appropriate braille heading to the brailleText object inputted. 539 ''' 540 keySignature = None 541 timeSignature = None 542 tempoText = None 543 metronomeMark = None 544 # find the first keySignature and timeSignature... 545 546 groupingKeysToProcess = self.groupingKeysToProcess or sorted(self.keys()) 547 548 while groupingKeysToProcess: 549 if groupingKeysToProcess[0].affinity > Affinity.MMARK: 550 break 551 cgk = groupingKeysToProcess.pop(0) # cgk = currentGroupingKey 552 553 cgkAffinityGroup = cgk.affinity 554 currentBrailleGrouping = self._groupingDict.get(cgk) # currentGrouping... 555 556 if cgkAffinityGroup == Affinity.SIGNATURE: 557 if len(currentBrailleGrouping) >= 2: 558 keySignature, timeSignature = (currentBrailleGrouping[0], 559 currentBrailleGrouping[1]) 560 elif len(currentBrailleGrouping) == 1: 561 keyOrTimeSig = currentBrailleGrouping[0] 562 if isinstance(keyOrTimeSig, key.KeySignature): 563 keySignature = keyOrTimeSig 564 else: 565 timeSignature = keyOrTimeSig 566 elif cgkAffinityGroup == Affinity.TTEXT: 567 tempoText = currentBrailleGrouping[0] 568 elif cgkAffinityGroup == Affinity.MMARK: 569 metronomeMark = currentBrailleGrouping[0] 570 571 if any([keySignature, timeSignature, tempoText, metronomeMark]): 572 brailleHeading = basic.transcribeHeading( 573 keySignature, 574 timeSignature, 575 tempoText, 576 metronomeMark, 577 maxLineLength=self.lineLength 578 ) 579 self.addHeading(brailleHeading) 580 581 582 # def extractInaccordGrouping(self): 583 # inaccords = self._groupingDict.get(self.currentGroupingKey) 584 # voice_trans = [] 585 # for music21Voice in inaccords: 586 # noteGrouping = extractBrailleElements(music21Voice) 587 # noteGrouping.descendingChords = inaccords.descendingChords 588 # noteGrouping.showClefSigns = inaccords.showClefSigns 589 # noteGrouping.upperFirstInNoteFingering = inaccords.upperFirstInNoteFingering 590 # voice_trans.append(ngMod.transcribeNoteGrouping(noteGrouping)) 591 # brailleInaccord = symbols['full_inaccord'].join(voice_trans) 592 # self.addInaccord(brailleInaccord) 593 594 595 def extractLongExpressionGrouping(self): 596 ''' 597 Extract the Long Expression that is in the ElementGrouping in cgk 598 and add it to brailleText. 599 ''' 600 cgk = self.currentGroupingKey 601 currentElementGrouping = self._groupingDict.get(cgk) 602 longTextExpression = currentElementGrouping[0] 603 longExprInBraille = basic.textExpressionToBraille(longTextExpression) 604 self.addLongExpression(longExprInBraille) 605 606 def showLeadingOctaveFromNoteGrouping(self, noteGrouping): 607 ''' 608 Given a noteGrouping, should we show the octave symbol? 609 610 >>> n1 = note.Note('C1') 611 >>> n2 = note.Note('D1') 612 >>> n3 = note.Note('E1') 613 614 >>> beg1 = braille.segment.BrailleElementGrouping([n1, n2, n3]) 615 >>> bs1 = braille.segment.BrailleSegment() 616 617 This is True because last note is None 618 619 >>> bs1.lastNote is None 620 True 621 >>> bs1.showLeadingOctaveFromNoteGrouping(beg1) 622 True 623 624 But if we run it again, now we have a note within a fourth, so we do not 625 need to show the octave: 626 627 >>> bs1.lastNote 628 <music21.note.Note E> 629 >>> bs1.showLeadingOctaveFromNoteGrouping(beg1) 630 False 631 632 And that is true no matter how many times we call it on the same 633 BrailleElementGrouping: 634 635 >>> bs1.showLeadingOctaveFromNoteGrouping(beg1) 636 False 637 638 But if we give a new, much higher BrailleElementGrouping, we 639 will see octave marks again. 640 641 >>> nHigh1 = note.Note('C6') 642 >>> nHigh2 = note.Note('D6') 643 >>> beg2 = braille.segment.BrailleElementGrouping([nHigh1, nHigh2]) 644 >>> bs1.showLeadingOctaveFromNoteGrouping(beg2) 645 True 646 647 But if we set `self.suppressOctaveMarks` to True, we won't see any 648 when we switch back to beg1: 649 650 >>> bs1.suppressOctaveMarks = True 651 >>> bs1.showLeadingOctaveFromNoteGrouping(beg2) 652 False 653 654 655 We also show octaves if for some reason two noteGroups in the same measure have 656 different BrailleElementGroupings keyed to consecutive ordinals. The code simulates 657 that situation. 658 659 >>> bs1.suppressOctaveMarks = False 660 >>> bs1.previousGroupingKey = braille.segment.SegmentKey(measure=3, ordinal=1, 661 ... affinity=braille.segment.Affinity.NOTEGROUP) 662 >>> bs1.currentGroupingKey = braille.segment.SegmentKey(measure=3, ordinal=2, 663 ... affinity=braille.segment.Affinity.NOTEGROUP) 664 >>> bs1.showLeadingOctaveFromNoteGrouping(beg2) 665 True 666 >>> bs1.showLeadingOctaveFromNoteGrouping(beg1) 667 True 668 >>> bs1.showLeadingOctaveFromNoteGrouping(beg1) 669 True 670 671 ''' 672 currentKey = self.currentGroupingKey 673 previousKey = self.previousGroupingKey 674 675 # if the previousKey did not exist 676 # or if the previousKey was not a collection of notes, 677 # or if the currentKey is split from the previous key for some reason 678 # while remaining in the same measure, then the lastNote is irrelevant 679 if (previousKey is not None 680 and currentKey is not None): 681 if (previousKey.affinity != Affinity.NOTEGROUP 682 or currentKey.affinity != Affinity.NOTEGROUP 683 or (currentKey.measure == previousKey.measure 684 and currentKey.ordinal == previousKey.ordinal + 1 685 and currentKey.hand == previousKey.hand)): 686 self.lastNote = None 687 688 if self.suppressOctaveMarks: 689 return False 690 691 # can't use Filter because noteGrouping is list-like not Stream-like 692 allNotes = [n for n in noteGrouping if isinstance(n, note.Note)] 693 showLeadingOctave = True 694 if allNotes: 695 if self.lastNote is not None: 696 firstNote = allNotes[0] 697 showLeadingOctave = basic.showOctaveWithNote(self.lastNote, firstNote) 698 # noinspection PyAttributeOutsideInit 699 self.lastNote = allNotes[-1] # probably should not be here... 700 701 return showLeadingOctave 702 703 def needsSplitToFit(self, brailleNoteGrouping) -> bool: 704 ''' 705 Returns boolean on whether a note grouping needs to be split in order to fit. 706 707 Generally a noteGrouping will need to be split if the amount of space left 708 is more than 1/4 of the line length and the brailleNoteGrouping cannot fit. 709 710 >>> n1 = note.Note('C1') 711 >>> n2 = note.Note('D1') 712 >>> n3 = note.Note('E1') 713 714 >>> beg1 = braille.segment.BrailleElementGrouping([n1, n2, n3]) 715 >>> seg = braille.segment.BrailleSegment() 716 >>> seg.needsSplitToFit(beg1) 717 False 718 >>> seg.lineLength = 10 719 >>> seg.needsSplitToFit(beg1) 720 True 721 ''' 722 quarterLineLength = self.lineLength // 4 723 spaceLeft = self.lineLength - self.currentLine.textLocation 724 if (spaceLeft > quarterLineLength 725 and len(brailleNoteGrouping) > quarterLineLength): 726 return True 727 else: 728 return False 729 730 def splitNoteGroupingAndTranscribe(self, 731 noteGrouping, 732 showLeadingOctaveOnFirst=False, 733 addSpaceToFirst=False): 734 ''' 735 Take a noteGrouping and split it at a logical place, 736 returning braille transcriptions of each section. 737 ''' 738 transcriber = ngMod.NoteGroupingTranscriber() 739 740 beatDivisionOffset = 0 741 REASONABLE_LIMIT = 10 742 (splitNoteGroupA, splitNoteGroupB) = (None, None) 743 brailleNoteGroupingA = None 744 745 while beatDivisionOffset < REASONABLE_LIMIT: 746 (splitNoteGroupA, splitNoteGroupB) = splitNoteGrouping( 747 noteGrouping, 748 beatDivisionOffset=beatDivisionOffset 749 ) 750 transcriber.showLeadingOctave = showLeadingOctaveOnFirst 751 splitNoteGroupA.withHyphen = True 752 brailleNoteGroupingA = transcriber.transcribeGroup(splitNoteGroupA) 753 if self.currentLine.canAppend(brailleNoteGroupingA, addSpace=addSpaceToFirst): 754 break 755 756 beatDivisionOffset += 1 757 continue 758 759 showLeadingOctave = not self.suppressOctaveMarks 760 transcriber.showLeadingOctave = showLeadingOctave 761 brailleNoteGroupingB = transcriber.transcribeGroup(splitNoteGroupB) 762 763 currentKey = self.currentGroupingKey 764 765 # noinspection PyProtectedMember 766 aKey = currentKey._replace(affinity=Affinity.SPLIT1_NOTEGROUP) 767 # noinspection PyProtectedMember 768 bKey = currentKey._replace(affinity=Affinity.SPLIT2_NOTEGROUP) 769 770 self[aKey] = splitNoteGroupA 771 self[bKey] = splitNoteGroupB 772 773 return (brailleNoteGroupingA, brailleNoteGroupingB) 774 775 def extractNoteGrouping(self): 776 ''' 777 Fundamentally important method that adds a noteGrouping to the braille line. 778 ''' 779 transcriber = ngMod.NoteGroupingTranscriber() 780 noteGrouping = self._groupingDict.get(self.currentGroupingKey) 781 782 showLeadingOctave = self.showLeadingOctaveFromNoteGrouping(noteGrouping) 783 transcriber.showLeadingOctave = showLeadingOctave 784 brailleNoteGrouping = transcriber.transcribeGroup(noteGrouping) 785 786 addSpace = self.optionalAddKeyboardSymbolsAndDots(brailleNoteGrouping) 787 788 if self.currentLine.canAppend(brailleNoteGrouping, addSpace=addSpace): 789 self.currentLine.append(brailleNoteGrouping, addSpace=addSpace) 790 else: 791 should_split: bool = self.needsSplitToFit(brailleNoteGrouping) 792 if should_split: 793 # there is too much space left in the current line to leave it blank 794 # but not enough space left to insert the current brailleNoteGrouping 795 # hence -- let us split this noteGrouping into two noteGroupings. 796 try: 797 bngA, bngB = self.splitNoteGroupingAndTranscribe(noteGrouping, 798 showLeadingOctave, 799 addSpace) 800 self.currentLine.append(bngA, addSpace=addSpace) 801 self.addToNewLine(bngB) 802 except BrailleSegmentException: 803 # No solutions possible 804 # Example: line length 10, chars used 7, remaining chars 3 805 # 25% of 10 is 2, so 3 is ordinarily too much space to leave blank 806 # But after trying to split, no solutions possible, since the first note 807 # requires 3 chars + space = 4 chars 808 # Give up and go to new line 809 should_split = False 810 811 if not should_split: 812 # not enough space left on this line to use, so 813 # move the whole group to another line 814 if showLeadingOctave is False and self.suppressOctaveMarks is False: 815 # if we didn't show the octave before, retranscribe with the octave 816 # displayed 817 transcriber.showLeadingOctave = True 818 brailleNoteGrouping = transcriber.transcribeGroup(noteGrouping) 819 # if not forceHyphen: 820 self.currentLine.lastHyphenToSpace() 821 self.addToNewLine(brailleNoteGrouping) 822 823 self.addRepeatSymbols(noteGrouping.numRepeats) 824 825 def addRepeatSymbols(self, repeatTimes): 826 ''' 827 Adds the appropriate number of repeat symbols, following DeGarmo chapter 17. 828 829 >>> seg = braille.segment.BrailleSegment() 830 >>> seg.addRepeatSymbols(0) 831 >>> print(seg.brailleText) 832 >>> seg.addRepeatSymbols(1) 833 >>> print(seg.brailleText) 834 ⠶ 835 836 >>> seg = braille.segment.BrailleSegment() 837 >>> seg.addRepeatSymbols(2) 838 >>> print(seg.brailleText) 839 ⠶⠀⠶ 840 841 >>> seg = braille.segment.BrailleSegment() 842 >>> seg.addRepeatSymbols(3) 843 >>> print(seg.brailleText) 844 ⠶⠼⠉ 845 846 Does not yet handle situations beginning with Example 17-6 (repeats at 847 different octaves), and further 848 ''' 849 if 0 < repeatTimes < 3: 850 for unused_repeatCounter in range(repeatTimes): 851 self.addSignatures(symbols['repeat']) 852 elif repeatTimes >= 3: # 17.3 -- repeat plus number. 853 self.addSignatures(symbols['repeat'] + basic.numberToBraille(repeatTimes)) 854 # noinspection PyAttributeOutsideInit 855 self.lastNote = None # this is set up to force an octave symbol on next note 856 857 def extractSignatureGrouping(self): 858 ''' 859 Extracts a key signature, time signature, and possibly an outgoing key signature 860 from the currentGroupingKey and adds it to the BrailleText object. 861 ''' 862 keySignature = None 863 timeSignature = None 864 865 cgk = self.currentGroupingKey 866 noteGrouping = self._groupingDict.get(cgk) 867 868 if len(noteGrouping) >= 2: 869 keySignature, timeSignature = noteGrouping[0], noteGrouping[1] 870 elif len(noteGrouping) == 1: 871 keyOrTimeSig = self._groupingDict.get(self.currentGroupingKey)[0] 872 if isinstance(keyOrTimeSig, key.KeySignature): 873 keySignature = keyOrTimeSig 874 else: 875 timeSignature = keyOrTimeSig 876 877 outgoingKeySig = None 878 if self.cancelOutgoingKeySig and keySignature is not None: 879 try: 880 outgoingKeySig = keySignature.outgoingKeySig 881 except AttributeError: 882 pass 883 884 brailleSig = basic.transcribeSignatures(keySignature, timeSignature, outgoingKeySig) 885 if brailleSig != '': 886 self.addSignatures(brailleSig) 887 888 def extractTempoTextGrouping(self): 889 ''' 890 extracts a tempo text and processes it... 891 ''' 892 self.groupingKeysToProcess.insert(0, self.currentGroupingKey) 893 if self.previousGroupingKey.affinity == Affinity.SIGNATURE: 894 self.groupingKeysToProcess.insert(0, self.previousGroupingKey) 895 self.extractHeading() 896 self.extractMeasureNumber() 897 898 def consolidate(self): 899 ''' 900 Puts together certain types of elements according to the last digit of their key 901 (if it is the same as Affinity.NOTEGROUP or not. 902 903 >>> SK = braille.segment.SegmentKey 904 >>> BS1 = braille.segment.BrailleSegment() 905 >>> BS1[SK(ordinal=0, affinity=2)] = ['hi', 'hello', 'there'] 906 >>> BS1[SK(ordinal=1, affinity=9)] = ['these', 'get'] 907 >>> BS1[SK(ordinal=2, affinity=9)] = ['put', 'together'] 908 >>> BS1[SK(ordinal=3, affinity=4)] = ['in', 'new', 'group'] 909 >>> BS1[SK(ordinal=4, affinity=9)] = ['with', 'the', 'previous'] 910 >>> BS2 = BS1.consolidate() 911 >>> for (groupingKey, groupingList) in sorted(BS2.items()): 912 ... print(groupingKey, groupingList) 913 SegmentKey(measure=0, ordinal=0, affinity=2, hand=None) ['hi', 'hello', 'there'] 914 SegmentKey(measure=0, ordinal=1, affinity=9, hand=None) these 915 get 916 put 917 together 918 SegmentKey(measure=0, ordinal=3, affinity=4, hand=None) ['in', 'new', 'group'] 919 SegmentKey(measure=0, ordinal=4, affinity=9, hand=None) with 920 the 921 previous 922 ''' 923 newSegment = BrailleSegment(self.lineLength) 924 pngKey = None 925 for (groupingKey, groupingList) in sorted(self.items()): 926 if groupingKey.affinity != Affinity.NOTEGROUP: 927 newSegment[groupingKey] = groupingList 928 pngKey = None 929 else: 930 if pngKey is None: 931 pngKey = groupingKey 932 for item in groupingList: 933 newSegment[pngKey].append(item) 934 return newSegment 935 936 def addGroupingAttributes(self): 937 ''' 938 Modifies the attributes of all :class:`~music21.braille.segment.BrailleElementGrouping` 939 instances in a list of :class:`~music21.braille.segment.BrailleSegment` instances. The 940 necessary information is retrieved from the segment and from the found clef, if any. 941 ''' 942 currentKeySig = key.KeySignature(0) 943 currentTimeSig = meter.TimeSignature('4/4') 944 945 allGroupings = sorted(self.items()) 946 (previousKey, previousList) = (None, None) 947 948 for (groupingKey, groupingList) in allGroupings: 949 if previousKey is not None: 950 if groupingKey.ordinal >= 1: 951 previousList.withHyphen = True 952 if (previousKey.ordinal == 0 953 and previousKey.affinity == Affinity.NOTEGROUP 954 and groupingKey.ordinal == 0 955 and groupingKey.affinity == Affinity.NOTEGROUP): 956 if isinstance(previousList[0], clef.Clef): 957 isRepetition = areGroupingsIdentical(previousList[1:], groupingList) 958 else: 959 isRepetition = areGroupingsIdentical(previousList, groupingList) 960 if isRepetition: 961 previousList.numRepeats += 1 962 del self[groupingKey] 963 continue 964 if groupingKey.affinity == Affinity.SIGNATURE: 965 for brailleElement in groupingList: 966 if isinstance(brailleElement, meter.TimeSignature): 967 currentTimeSig = brailleElement 968 elif isinstance(brailleElement, key.KeySignature): 969 brailleElement.outgoingKeySig = currentKeySig 970 currentKeySig = brailleElement 971 elif groupingKey.affinity == Affinity.NOTEGROUP: 972 if isinstance(groupingList[0], clef.Clef): 973 if isinstance(groupingList[0], (clef.TrebleClef, clef.AltoClef)): 974 self.descendingChords = True 975 elif isinstance(groupingList[0], (clef.BassClef, clef.TenorClef)): 976 self.descendingChords = False 977 978 # make a whole rest no matter the length of the rest if only one note. 979 allGeneralNotes = [n for n in groupingList if isinstance(n, note.GeneralNote)] 980 if len(allGeneralNotes) == 1 and isinstance(allGeneralNotes[0], note.Rest): 981 allGeneralNotes[0].fullMeasure = True 982 groupingList.keySignature = currentKeySig 983 groupingList.timeSignature = currentTimeSig 984 groupingList.descendingChords = self.descendingChords 985 groupingList.showClefSigns = self.showClefSigns 986 groupingList.upperFirstInNoteFingering = self.upperFirstInNoteFingering 987 (previousKey, previousList) = (groupingKey, groupingList) 988 if self.endHyphen: 989 previousList.withHyphen = True 990 991 def fixArticulations(self): 992 ''' 993 Goes through each :class:`~music21.braille.segment.BrailleSegment` and modifies the 994 list of :attr:`~music21.note.GeneralNote.articulations` of a :class:`~music21.note.Note` 995 if appropriate. In particular, two rules are applied: 996 997 * Doubling rule => If four or more of the same :class:`~music21.articulations.Articulation` 998 are found in a row, the first instance of the articulation is doubled and the rest are 999 omitted. 1000 1001 * Staccato, Tenuto rule => "If two repeated notes appear to be tied, but either is marked 1002 staccato or tenuto, they are treated as slurred instead of tied." (BMTM, 112) 1003 ''' 1004 from music21 import articulations 1005 1006 def fixOneArticulation(artic, music21NoteStart, allNotes, noteIndexStart): 1007 articName = artic.name 1008 if articName == 'fingering': # fingerings are not considered articulations... 1009 return 1010 if (isinstance(artic, (articulations.Staccato, articulations.Tenuto)) 1011 and music21NoteStart.tie is not None): 1012 if music21NoteStart.tie.type == 'stop': 1013 allNotes[noteIndexStart - 1].tie = None 1014 allNotes[noteIndexStart - 1].shortSlur = True 1015 else: 1016 allNotes[noteIndexStart + 1].tie = None 1017 music21NoteStart.shortSlur = True 1018 music21NoteStart.tie = None 1019 numSequential = 0 1020 for noteIndexContinue in range(noteIndexStart + 1, len(allNotes)): 1021 music21NoteContinue = allNotes[noteIndexContinue] 1022 if articName in [a.name for a in music21NoteContinue.articulations]: 1023 numSequential += 1 1024 continue 1025 break 1026 if numSequential < 3: 1027 return 1028 # else: 1029 # double the articulation on the first note and remove from the next... 1030 music21NoteStart.articulations.append(artic) 1031 for noteIndexContinue in range(noteIndexStart + 1, 1032 noteIndexStart + numSequential): 1033 music21NoteContinue = allNotes[noteIndexContinue] 1034 for artOther in music21NoteContinue.articulations: 1035 if artOther.name == articName: 1036 music21NoteContinue.articulations.remove(artOther) 1037 1038 newSegment = self.consolidate() 1039 noteGroupings = [newSegment[gpKey] 1040 for gpKey in newSegment.keys() 1041 if gpKey.affinity == Affinity.NOTEGROUP] 1042 for noteGrouping in noteGroupings: 1043 allNotes_outer = [n for n in noteGrouping if isinstance(n, note.Note)] 1044 for noteIndexStart_outer in range(len(allNotes_outer)): 1045 music21NoteStart_outer = allNotes_outer[noteIndexStart_outer] 1046 for artic_outer in music21NoteStart_outer.articulations: 1047 fixOneArticulation( 1048 artic_outer, 1049 music21NoteStart_outer, 1050 allNotes_outer, 1051 noteIndexStart_outer 1052 ) 1053 1054 1055class BrailleGrandSegment(BrailleSegment, text.BrailleKeyboard): 1056 ''' 1057 A BrailleGrandSegment represents a pair of segments (rightSegment, leftSegment) 1058 representing the right and left hands of a piano staff (or other two-staff object). 1059 1060 >>> bgs = braille.segment.BrailleGrandSegment(lineLength=36) 1061 >>> bgs.lineLength 1062 36 1063 ''' 1064 def __init__(self, lineLength: int = 40): 1065 BrailleSegment.__init__(self, lineLength=lineLength) 1066 text.BrailleKeyboard.__init__(self, lineLength=lineLength) 1067 self.allKeyPairs = [] 1068 self.previousGroupingPair = None 1069 self.currentGroupingPair = None 1070 1071 @property 1072 def brailleText(self): 1073 return text.BrailleKeyboard.__str__(self) 1074 1075 def __str__(self): 1076 name = '<music21.braille.segment BrailleGrandSegment>\n===' 1077 allPairs = [] 1078 for (rightKey, leftKey) in self.yieldCombinedGroupingKeys(): 1079 if rightKey is not None: 1080 rightHeading = 'Measure {0} Right, {1} {2}:\n'.format( 1081 rightKey.measure, affinityNames[rightKey.affinity], rightKey.ordinal + 1) 1082 rightContents = str(self._groupingDict.get(rightKey)) 1083 rightFull = ''.join([rightHeading, rightContents]) 1084 else: 1085 rightFull = '' 1086 if leftKey is not None: 1087 leftHeading = '\nMeasure {0} Left, {1} {2}:\n'.format( 1088 leftKey.measure, affinityNames[leftKey.affinity], leftKey.ordinal + 1) 1089 leftContents = str(self._groupingDict.get(leftKey)) 1090 leftFull = ''.join([leftHeading, leftContents]) 1091 else: 1092 leftFull = '' 1093 allPairs.append('\n'.join([rightFull, leftFull, '====\n'])) 1094 out = '\n'.join(['---begin grand segment---', name, ''.join(allPairs), 1095 '---end grand segment---']) 1096 return out 1097 1098 def yieldCombinedGroupingKeys(self): 1099 ''' 1100 yields all the keys in order as a tuple of (rightKey, leftKey) where 1101 two keys are grouped if they have the same segmentKey except for the hand. 1102 1103 >>> bgs = braille.segment.BrailleGrandSegment() 1104 >>> SegmentKey = braille.segment.SegmentKey # namedtuple 1105 >>> bgs[SegmentKey(1, 1, 1, 'right')] = '1r' 1106 >>> bgs[SegmentKey(1, 1, 1, 'left')] = '1l' 1107 >>> bgs[SegmentKey(1, 2, 3, 'right')] = '2r' 1108 >>> bgs[SegmentKey(1, 2, 4, 'left')] = '3l' 1109 >>> bgs[SegmentKey(2, 1, 9, 'left')] = '4l' 1110 >>> bgs[SegmentKey(2, 1, 9, 'right')] = '4r' 1111 >>> bgs[SegmentKey(3, 1, 9, 'right')] = '5r' 1112 >>> for l, r in bgs.yieldCombinedGroupingKeys(): 1113 ... (bgs[l], bgs[r]) 1114 ('1r', '1l') 1115 ('2r', <music21.braille.segment.BrailleElementGrouping []>) 1116 (<music21.braille.segment.BrailleElementGrouping []>, '3l') 1117 ('4r', '4l') 1118 ('5r', <music21.braille.segment.BrailleElementGrouping []>) 1119 ''' 1120 def segmentKeySortKey(segmentKey): 1121 ''' 1122 sort by measure, then ordinal, then affinity, then hand (r then l) 1123 ''' 1124 if segmentKey.hand == 'right': 1125 skH = -1 1126 else: 1127 skH = 1 1128 return (segmentKey.measure, segmentKey.ordinal, segmentKey.affinity, skH) 1129 1130 def matchOther(thisKey_inner, otherKey): 1131 if (thisKey_inner.measure == otherKey.measure 1132 and thisKey_inner.ordinal == otherKey.ordinal 1133 and thisKey_inner.affinity == otherKey.affinity): 1134 return True 1135 else: 1136 return False 1137 1138 storedRight = None 1139 storedLeft = None 1140 for thisKey in sorted(self.keys(), key=segmentKeySortKey): 1141 if thisKey.hand == 'right': 1142 if storedLeft is not None: 1143 if matchOther(thisKey, storedLeft): 1144 yield(thisKey, storedLeft) 1145 elif (thisKey.affinity == Affinity.NOTEGROUP 1146 and matchOther(thisKey._replace(affinity=Affinity.INACCORD), storedLeft)): 1147 # r.h. notegroup goes before an lh inaccord, despite this being out of order 1148 yield(thisKey, storedLeft) 1149 else: 1150 yield(None, storedLeft) 1151 storedRight = thisKey 1152 storedLeft = None 1153 else: 1154 storedRight = thisKey 1155 elif thisKey.hand == 'left': 1156 if storedRight is not None: 1157 if matchOther(thisKey, storedRight): 1158 yield(storedRight, thisKey) 1159 elif storedRight.affinity < Affinity.INACCORD: 1160 yield(storedRight, None) 1161 yield(None, thisKey) 1162 else: 1163 yield(storedRight, None) 1164 storedLeft = thisKey 1165 storedRight = None 1166 else: 1167 storedLeft = thisKey 1168 1169 if storedRight: 1170 yield (storedRight, None) 1171 if storedLeft: 1172 yield (None, storedLeft) 1173 1174 # def combineGroupingKeys(self, rightSegment, leftSegment): 1175 # # return list(self.yieldCombinedGroupingKeys()) 1176 # 1177 # groupingKeysRight = sorted(rightSegment.keys()) 1178 # groupingKeysLeft = sorted(leftSegment.keys()) 1179 # combinedGroupingKeys = [] 1180 # 1181 # while groupingKeysRight: 1182 # gkRight = groupingKeysRight.pop(0) 1183 # try: 1184 # groupingKeysLeft.remove(gkRight) 1185 # combinedGroupingKeys.append((gkRight, gkRight)) 1186 # except ValueError: 1187 # if gkRight.affinity < Affinity.INACCORD: 1188 # combinedGroupingKeys.append((gkRight, None)) 1189 # else: 1190 # if gkRight.affinity == Affinity.INACCORD: 1191 # gkLeft = gkRight._replace(affinity=gkRight.affinity + 1) 1192 # else: 1193 # gkLeft = gkRight._replace(affinity=gkRight.affinity - 1) 1194 # try: 1195 # groupingKeysLeft.remove(gkLeft) 1196 # except ValueError: 1197 # raise BrailleSegmentException( 1198 # 'Misaligned braille groupings: ' + 1199 # 'groupingKeyLeft was %s' % gkLeft + 1200 # 'groupingKeyRight was %s' % gkRight + 1201 # 'rightSegment was %s, leftSegment was %s' % 1202 # (rightSegment, leftSegment)) 1203 # 1204 # try: 1205 # combinedGroupingTuple = (gkRight, gkLeft) 1206 # combinedGroupingKeys.append(combinedGroupingTuple) 1207 # except ValueError: 1208 # raise BrailleSegmentException( 1209 # 'Misaligned braille groupings could not append combinedGroupingKeys') 1210 # 1211 # 1212 # while groupingKeysLeft: 1213 # gkLeft = groupingKeysLeft.pop(0) 1214 # combinedGroupingTuple = (None, gkLeft) 1215 # combinedGroupingKeys.append(combinedGroupingTuple) 1216 # 1217 # return combinedGroupingKeys 1218 1219 1220 def transcribe(self): 1221 ''' 1222 Returns the BrailleText from the combined grouping keys 1223 ''' 1224 self.allKeyPairs = list(self.yieldCombinedGroupingKeys()) 1225 lastPair = self.allKeyPairs[-1] 1226 highestMeasure = lastPair[0].measure if lastPair[0] else lastPair[1].measure 1227 self.highestMeasureNumberLength = len(str(highestMeasure)) 1228 1229 self.extractHeading() # Heading 1230 self.currentGroupingPair = None 1231 while self.allKeyPairs: 1232 self.previousGroupingPair = self.currentGroupingPair 1233 self.currentGroupingPair = self.allKeyPairs.pop(0) 1234 (rightKey, leftKey) = self.currentGroupingPair 1235 1236 if ((rightKey is not None and rightKey.affinity >= Affinity.INACCORD) 1237 or (leftKey is not None and leftKey.affinity >= Affinity.INACCORD)): 1238 self.extractNoteGrouping() # Note or Inaccord Grouping 1239 # elif (rightKey.affinity == Affinity.SIGNATURE 1240 # or leftKey.affinity == Affinity.SIGNATURE): 1241 # self.extractSignatureGrouping() # Signature Grouping 1242 # elif (rightKey.affinity == Affinity.LONG_TEXTEXPR 1243 # or leftKey.affinity == Affinity.LONG_TEXTEXPR): 1244 # self.extractLongExpressionGrouping() # Long Expression Grouping 1245 # elif rightKey.affinity == Affinity.TTEXT or leftKey.affinity == Affinity.TTEXT: 1246 # self.extractTempoTextGrouping() # Tempo Text Grouping 1247 return self.brailleText 1248 1249 def extractHeading(self): 1250 ''' 1251 Finds KeySignatures, TimeSignatures, TempoText, and Metronome Marks 1252 within the keyPairs, and removes some from allKeyPairs. 1253 ''' 1254 keySignature = None 1255 timeSignature = None 1256 tempoText = None 1257 metronomeMark = None 1258 1259 while True: 1260 (rightKey, leftKey) = self.allKeyPairs[0] 1261 useKey = rightKey 1262 try: 1263 useElement = self._groupingDict.get(rightKey) 1264 except KeyError as ke: 1265 if ke.args[0] == 'None': 1266 useElement = self._groupingDict.get(leftKey) 1267 useKey = leftKey 1268 else: 1269 raise ke 1270 if useKey.affinity > Affinity.MMARK: 1271 break 1272 self.allKeyPairs.pop(0) 1273 if useKey.affinity == Affinity.SIGNATURE: 1274 try: 1275 keySignature, timeSignature = useElement[0], useElement[1] 1276 except IndexError: 1277 if isinstance(useElement, key.KeySignature): 1278 keySignature = useElement[0] 1279 else: 1280 timeSignature = useElement[0] 1281 elif useKey.affinity == Affinity.TTEXT: 1282 tempoText = useElement[0] 1283 elif useKey.affinity == Affinity.MMARK: 1284 metronomeMark = useElement[0] 1285 1286 try: 1287 brailleHeading = basic.transcribeHeading( 1288 keySignature, 1289 timeSignature, 1290 tempoText, 1291 metronomeMark, 1292 maxLineLength=self.lineLength 1293 ) 1294 self.addHeading(brailleHeading) 1295 except basic.BrailleBasicException as bbe: 1296 if bbe.args[0] != 'No heading can be made.': 1297 raise bbe 1298 1299 def extractNoteGrouping(self): 1300 (rightKey, leftKey) = self.currentGroupingPair 1301 if rightKey: 1302 mNum = rightKey.measure 1303 elif leftKey: 1304 mNum = leftKey.measure 1305 else: 1306 raise ValueError('Measure must be defined for leftKey or rightKey') 1307 1308 currentMeasureNumber = basic.numberToBraille(mNum, withNumberSign=False) 1309 1310 def brailleFromKey(rightOrLeftKey): 1311 if rightOrLeftKey is not None and rightOrLeftKey.affinity == Affinity.INACCORD: 1312 inaccords = self._groupingDict.get(rightOrLeftKey) 1313 voice_trans = [] 1314 for music21Voice in inaccords: 1315 noteGrouping = extractBrailleElements(music21Voice) 1316 noteGrouping.descendingChords = inaccords.descendingChords 1317 noteGrouping.showClefSigns = inaccords.showClefSigns 1318 noteGrouping.upperFirstInNoteFingering = inaccords.upperFirstInNoteFingering 1319 voice_trans.append(ngMod.transcribeNoteGrouping(noteGrouping)) 1320 brailleStr = symbols['full_inaccord'].join(voice_trans) 1321 elif rightOrLeftKey is not None: 1322 brailleStr = ngMod.transcribeNoteGrouping(self._groupingDict.get(rightOrLeftKey)) 1323 else: 1324 brailleStr = '' 1325 1326 return brailleStr 1327 1328 rhBraille = brailleFromKey(rightKey) 1329 lhBraille = brailleFromKey(leftKey) 1330 1331 self.addNoteGroupings(currentMeasureNumber, rhBraille, lhBraille) 1332 1333 # # noinspection PyUnusedLocal 1334 # def extractSignatureGrouping(self, brailleKeyboard): 1335 # pass 1336 # 1337 # # noinspection PyUnusedLocal 1338 # def extractLongExpressionGrouping(self, brailleKeyboard): 1339 # pass 1340 # 1341 # # noinspection PyUnusedLocal 1342 # def extractTempoTextGrouping(self, brailleKeyboard): 1343 # pass 1344 1345 1346# ------------------------------------------------------------------------------ 1347# Grouping + Segment creation from music21.stream Part 1348 1349def findSegments(music21Part, 1350 *, 1351 setHand=None, 1352 cancelOutgoingKeySig=True, 1353 descendingChords=None, 1354 dummyRestLength=None, 1355 maxLineLength=40, 1356 segmentBreaks=None, 1357 showClefSigns=False, 1358 showFirstMeasureNumber=True, 1359 showHand=None, 1360 showHeading=True, 1361 showLongSlursAndTiesTogether: Optional[bool] = None, 1362 showShortSlursAndTiesTogether=False, 1363 slurLongPhraseWithBrackets=True, 1364 suppressOctaveMarks=False, 1365 upperFirstInNoteFingering=True, 1366 ): 1367 ''' 1368 Takes in a :class:`~music21.stream.Part`. 1369 1370 Returns a list of :class:`~music21.segment.BrailleSegment` instances. 1371 1372 Five functions or methods get called in the generation of segments: 1373 1374 * :func:`~music21.braille.segment.prepareSlurredNotes` 1375 * :func:`~music21.braille.segment.getRawSegments` 1376 * :meth:`~music21.braille.segment.BrailleSegment.addGroupingAttributes` 1377 * :meth:`~music21.braille.segment.BrailleSegment.addSegmentAttributes` 1378 * :meth:`~music21.braille.segment.BrailleSegment.fixArticulations` 1379 1380 >>> from music21.braille import test 1381 >>> example = test.example11_2() 1382 >>> allSegments = braille.segment.findSegments(example) 1383 1384 >>> print(str(allSegments[0])) 1385 ---begin segment--- 1386 <music21.braille.segment BrailleSegment> 1387 Measure 0, Signature Grouping 1: 1388 <music21.key.KeySignature of 3 flats> 1389 <music21.meter.TimeSignature 4/4> 1390 === 1391 Measure 0, Note Grouping 1: 1392 <music21.clef.TrebleClef> 1393 <music21.note.Note B-> 1394 === 1395 Measure 1, Note Grouping 1: 1396 <music21.note.Note G> 1397 <music21.note.Note E-> 1398 <music21.note.Note D> 1399 <music21.note.Note E-> 1400 === 1401 Measure 2, Note Grouping 1: 1402 <music21.note.Note G> 1403 <music21.note.Note F> 1404 <music21.note.Note E-> 1405 === 1406 Measure 3, Note Grouping 1: 1407 <music21.note.Note A-> 1408 <music21.note.Note G> 1409 <music21.note.Note C> 1410 <music21.note.Note C> 1411 === 1412 Measure 4, Note Grouping 1: 1413 <music21.note.Note B-> 1414 <music21.note.Note B-> 1415 === 1416 Measure 5, Note Grouping 1: 1417 <music21.note.Note E-> 1418 <music21.note.Note B-> 1419 <music21.note.Note A-> 1420 <music21.note.Note G> 1421 === 1422 Measure 6, Note Grouping 1: 1423 <music21.note.Note G> 1424 <music21.note.Note F> 1425 <music21.note.Note C> 1426 === 1427 Measure 7, Note Grouping 1: 1428 <music21.note.Note C> 1429 <music21.note.Note F> 1430 <music21.note.Note A-> 1431 <music21.note.Note D> 1432 === 1433 Measure 8, Note Grouping 1: 1434 <music21.note.Note E-> 1435 music hyphen ⠐ 1436 === 1437 ---end segment--- 1438 1439 1440 Second segment 1441 1442 >>> print(str(allSegments[1])) 1443 ---begin segment--- 1444 <music21.braille.segment BrailleSegment> 1445 Measure 8, Note Grouping 2: 1446 <music21.note.Note G> 1447 === 1448 Measure 9, Note Grouping 1: 1449 <music21.note.Note G> 1450 <music21.note.Note F> 1451 <music21.note.Note F> 1452 <music21.note.Note F> 1453 === 1454 Measure 10, Note Grouping 1: 1455 <music21.note.Note A-> 1456 <music21.note.Note G> 1457 <music21.note.Note B-> 1458 === 1459 Measure 11, Note Grouping 1: 1460 <music21.note.Note B-> 1461 <music21.note.Note A> 1462 <music21.note.Note A> 1463 <music21.note.Note C> 1464 === 1465 Measure 12, Note Grouping 1: 1466 <music21.note.Note B-> 1467 <music21.note.Note B-> 1468 === 1469 Measure 13, Note Grouping 1: 1470 <music21.note.Note E-> 1471 <music21.note.Note B-> 1472 <music21.note.Note A-> 1473 <music21.note.Note G> 1474 === 1475 Measure 14, Note Grouping 1: 1476 <music21.note.Note G> 1477 <music21.note.Note F> 1478 <music21.note.Note C> 1479 === 1480 Measure 15, Note Grouping 1: 1481 <music21.note.Note C> 1482 <music21.note.Rest quarter> 1483 <music21.note.Note F> 1484 <music21.note.Rest quarter> 1485 === 1486 Measure 16, Note Grouping 1: 1487 <music21.note.Note A-> 1488 <music21.note.Note D> 1489 === 1490 Measure 17, Note Grouping 1: 1491 <music21.note.Note E-> 1492 <music21.bar.Barline type=final> 1493 === 1494 ---end segment--- 1495 ''' 1496 # Slurring 1497 # -------- 1498 prepareSlurredNotes(music21Part, 1499 showLongSlursAndTiesTogether=showLongSlursAndTiesTogether, 1500 showShortSlursAndTiesTogether=showShortSlursAndTiesTogether, 1501 slurLongPhraseWithBrackets=slurLongPhraseWithBrackets, 1502 ) 1503 1504 # Raw Segments 1505 # ------------ 1506 allSegments = getRawSegments(music21Part, setHand=setHand, maxLineLength=maxLineLength) 1507 1508 for seg in allSegments: 1509 # Grouping Attributes 1510 # ------------------- 1511 seg.showClefSigns = showClefSigns 1512 seg.upperFirstInNoteFingering = upperFirstInNoteFingering 1513 seg.descendingChords = descendingChords 1514 seg.addGroupingAttributes() 1515 1516 # Segment Attributes 1517 # ------------------ 1518 seg.cancelOutgoingKeySig = cancelOutgoingKeySig 1519 seg.dummyRestLength = dummyRestLength 1520 seg.showFirstMeasureNumber = showFirstMeasureNumber 1521 seg.showHand = showHand 1522 seg.showHeading = showHeading 1523 seg.suppressOctaveMarks = suppressOctaveMarks 1524 1525 # Articulations 1526 # ------------- 1527 seg.fixArticulations() 1528 1529 return allSegments 1530 1531 1532def prepareSlurredNotes(music21Part, 1533 *, 1534 slurLongPhraseWithBrackets=True, 1535 showShortSlursAndTiesTogether=False, 1536 showLongSlursAndTiesTogether: Optional[bool] = None, 1537 ): 1538 ''' 1539 Takes in a :class:`~music21.stream.Part` and three keywords: 1540 1541 * slurLongPhraseWithBrackets 1542 * showShortSlursAndTiesTogether 1543 * showLongSlursAndTiesTogether 1544 1545 For any slurs present in the Part, the appropriate notes are labeled 1546 with attributes indicating where to put the symbols that represent 1547 slurring in braille. For purposes of slurring in braille, there is 1548 a distinction between short and long phrases. In a short phrase, a 1549 slur covers up to four notes. A short slur symbol should follow each 1550 note except the last. 1551 1552 1553 >>> import copy 1554 >>> from music21.braille import segment 1555 >>> short = converter.parse('tinynotation: 3/4 c4 d e') 1556 >>> s1 = spanner.Slur(short.recurse().notes.first(), short.recurse().notes.last()) 1557 >>> short.append(s1) 1558 >>> short.show('text') 1559 {0.0} <music21.stream.Measure 1 offset=0.0> 1560 {0.0} <music21.clef.TrebleClef> 1561 {0.0} <music21.meter.TimeSignature 3/4> 1562 {0.0} <music21.note.Note C> 1563 {1.0} <music21.note.Note D> 1564 {2.0} <music21.note.Note E> 1565 {3.0} <music21.bar.Barline type=final> 1566 {3.0} <music21.spanner.Slur <music21.note.Note C><music21.note.Note E>> 1567 >>> shortA = copy.deepcopy(short) 1568 >>> segment.prepareSlurredNotes(shortA) 1569 >>> shortA.recurse().notes[0].shortSlur 1570 True 1571 >>> shortA.recurse().notes[1].shortSlur 1572 True 1573 1574 1575 In a long phrase, a slur covers more than four notes. There are two 1576 options for slurring long phrases. The first is by using the bracket 1577 slur. By default, slurLongPhraseWithBrackets is True. The opening 1578 bracket sign is put before the first note, and the closing bracket 1579 sign is put before the last note. 1580 1581 1582 >>> long = converter.parse('tinynotation: 3/4 c8 d e f g a') 1583 >>> s2 = spanner.Slur(long[note.Note].first(), long[note.Note].last()) 1584 >>> long.append(s2) 1585 >>> long.show('text') 1586 {0.0} <music21.stream.Measure 1 offset=0.0> 1587 {0.0} <music21.clef.TrebleClef> 1588 {0.0} <music21.meter.TimeSignature 3/4> 1589 {0.0} <music21.note.Note C> 1590 {0.5} <music21.note.Note D> 1591 {1.0} <music21.note.Note E> 1592 {1.5} <music21.note.Note F> 1593 {2.0} <music21.note.Note G> 1594 {2.5} <music21.note.Note A> 1595 {3.0} <music21.bar.Barline type=final> 1596 {3.0} <music21.spanner.Slur <music21.note.Note C><music21.note.Note A>> 1597 >>> longA = copy.deepcopy(long) 1598 >>> segment.prepareSlurredNotes(longA) 1599 >>> longA[note.Note].first().beginLongBracketSlur 1600 True 1601 >>> longA[note.Note].last().endLongBracketSlur 1602 True 1603 1604 1605 The other way is by using the double slur, setting `slurLongPhraseWithBrackets` 1606 to False. The opening sign of the double slur is put after the first note 1607 (i.e. before the second note) and the closing sign is put before the last 1608 note (i.e. before the second to last note). 1609 1610 If a value is not supplied for `showLongSlursAndTiesTogether`, it will take the 1611 value set for `slurLongPhraseWithBrackets`, which defaults True. 1612 1613 1614 >>> longB = copy.deepcopy(long) 1615 >>> segment.prepareSlurredNotes(longB, slurLongPhraseWithBrackets=False) 1616 >>> longB.recurse().notes[1].beginLongDoubleSlur 1617 True 1618 >>> longB.recurse().notes[-2].endLongDoubleSlur 1619 True 1620 1621 1622 In the event that slurs and ties are shown together in print, the slur is 1623 redundant. Examples are shown for slurring a short phrase; the process is 1624 identical for slurring a long phrase. 1625 1626 1627 Below, a tie has been added between the first two notes of the short phrase 1628 defined above. If showShortSlursAndTiesTogether is set to its default value of 1629 False, then the slur on either side of the phrase is reduced by the amount that 1630 ties are present, as shown below. 1631 1632 1633 >>> short.recurse().notes[0].tie = tie.Tie('start') 1634 >>> shortB = copy.deepcopy(short) 1635 >>> segment.prepareSlurredNotes(shortB) 1636 >>> shortB.recurse().notes[0].shortSlur 1637 Traceback (most recent call last): 1638 AttributeError: 'Note' object has no attribute 'shortSlur' 1639 >>> shortB.recurse().notes[0].tie 1640 <music21.tie.Tie start> 1641 >>> shortB.recurse().notes[1].shortSlur 1642 True 1643 1644 1645 If showShortSlursAndTiesTogether is set to True, then the slurs and ties are 1646 shown together (i.e. the note has both a shortSlur and a tie). 1647 1648 >>> shortC = copy.deepcopy(short) 1649 >>> segment.prepareSlurredNotes(shortC, showShortSlursAndTiesTogether=True) 1650 >>> shortC.recurse().notes[0].shortSlur 1651 True 1652 >>> shortC.recurse().notes[0].tie 1653 <music21.tie.Tie start> 1654 1655 TODO: This should not add attributes to Note objects but instead return a collection 1656 of sets of notes that have each element applied to it. 1657 ''' 1658 if not music21Part.spannerBundle: 1659 return 1660 if showLongSlursAndTiesTogether is None: 1661 showLongSlursAndTiesTogether = slurLongPhraseWithBrackets 1662 1663 allNotes = music21Part.flatten().notes.stream() 1664 for slur in music21Part.spannerBundle.getByClass(spanner.Slur): 1665 firstNote = slur.getFirst() 1666 lastNote = slur.getLast() 1667 1668 try: 1669 beginIndex = allNotes.index(firstNote) 1670 endIndex = allNotes.index(lastNote) 1671 except exceptions21.StreamException: # pragma: no cover 1672 # there might be a case where a slur is present in a Stream but where 1673 # the elements of the slur are no longer present in the stream, 1674 # such as if they were manually removed. It is rare, but why this is here. 1675 continue 1676 1677 delta = abs(endIndex - beginIndex) + 1 1678 1679 if not showShortSlursAndTiesTogether and delta <= SEGMENT_MAXNOTESFORSHORTSLUR: 1680 # normally slurs are not shown on tied notes (unless 1681 # showShortSlursAndTiesTogether is True, for facsimile transcriptions). 1682 if (allNotes[beginIndex].tie is not None 1683 and allNotes[beginIndex].tie.type == 'start'): 1684 beginIndex += 1 1685 if allNotes[endIndex].tie is not None and allNotes[endIndex].tie.type == 'stop': 1686 endIndex -= 1 1687 1688 if not showLongSlursAndTiesTogether and delta > SEGMENT_MAXNOTESFORSHORTSLUR: 1689 if (allNotes[beginIndex].tie is not None 1690 and allNotes[beginIndex].tie.type == 'start'): 1691 beginIndex += 1 1692 if allNotes[endIndex].tie is not None and allNotes[endIndex].tie.type == 'stop': 1693 endIndex -= 1 1694 1695 if delta <= SEGMENT_MAXNOTESFORSHORTSLUR: 1696 for noteIndex in range(beginIndex, endIndex): 1697 allNotes[noteIndex].shortSlur = True 1698 else: 1699 if slurLongPhraseWithBrackets: 1700 allNotes[beginIndex].beginLongBracketSlur = True 1701 allNotes[endIndex].endLongBracketSlur = True 1702 else: 1703 allNotes[beginIndex + 1].beginLongDoubleSlur = True 1704 allNotes[endIndex - 1].endLongDoubleSlur = True 1705 1706 1707def getRawSegments(music21Part, 1708 *, 1709 setHand=None, 1710 maxLineLength: int = 40, 1711 ): 1712 ''' 1713 Takes in a :class:`~music21.stream.Part`, divides it up into segments (i.e. instances of 1714 :class:`~music21.braille.segment.BrailleSegment`). This function assumes 1715 that the Part is already divided up into measures 1716 (see :class:`~music21.stream.Measure`). An acceptable input is shown below. 1717 1718 This will automatically find appropriate segment breaks at 1719 :class:`~music21.braille.objects.BrailleSegmentDivision` 1720 or :class:`~music21.braille.objects.BrailleOptionalSegmentDivision` 1721 or after 48 elements if a double bar or repeat sign is encountered. 1722 1723 Two functions are called for each measure during the creation of segments: 1724 1725 * :func:`~music21.braille.segment.prepareBeamedNotes` 1726 * :func:`~music21.braille.segment.extractBrailleElements` 1727 1728 >>> tn = converter.parse("tinynotation: 3/4 c4 c c e e e g g g c'2.") 1729 >>> tn = tn.makeNotation(cautionaryNotImmediateRepeat=False) 1730 >>> tn.show('text') 1731 {0.0} <music21.stream.Measure 1 offset=0.0> 1732 {0.0} <music21.clef.TrebleClef> 1733 {0.0} <music21.meter.TimeSignature 3/4> 1734 {0.0} <music21.note.Note C> 1735 {1.0} <music21.note.Note C> 1736 {2.0} <music21.note.Note C> 1737 {3.0} <music21.stream.Measure 2 offset=3.0> 1738 {0.0} <music21.note.Note E> 1739 {1.0} <music21.note.Note E> 1740 {2.0} <music21.note.Note E> 1741 {6.0} <music21.stream.Measure 3 offset=6.0> 1742 {0.0} <music21.note.Note G> 1743 {1.0} <music21.note.Note G> 1744 {2.0} <music21.note.Note G> 1745 {9.0} <music21.stream.Measure 4 offset=9.0> 1746 {0.0} <music21.note.Note C> 1747 {3.0} <music21.bar.Barline type=final> 1748 1749 By default, there is no break anywhere within the Part, 1750 and a segmentList of size 1 is returned. 1751 1752 >>> import copy 1753 >>> from music21.braille import segment 1754 >>> tnA = copy.deepcopy(tn) 1755 >>> rawSegments = segment.getRawSegments(tnA) 1756 >>> len(rawSegments) 1757 1 1758 >>> rawSegments[0] 1759 <music21.braille.segment.BrailleSegment 1 line, 0 headings, 40 cols> 1760 1761 >>> print(rawSegments[0]) 1762 ---begin segment--- 1763 <music21.braille.segment BrailleSegment> 1764 Measure 1, Signature Grouping 1: 1765 <music21.meter.TimeSignature 3/4> 1766 === 1767 Measure 1, Note Grouping 1: 1768 <music21.clef.TrebleClef> 1769 <music21.note.Note C> 1770 <music21.note.Note C> 1771 <music21.note.Note C> 1772 === 1773 Measure 2, Note Grouping 1: 1774 <music21.note.Note E> 1775 <music21.note.Note E> 1776 <music21.note.Note E> 1777 === 1778 Measure 3, Note Grouping 1: 1779 <music21.note.Note G> 1780 <music21.note.Note G> 1781 <music21.note.Note G> 1782 === 1783 Measure 4, Note Grouping 1: 1784 <music21.note.Note C> 1785 <music21.bar.Barline type=final> 1786 === 1787 ---end segment--- 1788 1789 Now, a segment break occurs at measure 2, offset 1.0 within that measure. 1790 The two segments are shown below. 1791 1792 >>> tnB = copy.deepcopy(tn) 1793 >>> tnB.measure(2).insert(1.0, braille.objects.BrailleSegmentDivision()) 1794 >>> allSegments = segment.getRawSegments(tnB) 1795 >>> len(allSegments) 1796 2 1797 1798 >>> allSegments[0] 1799 <music21.braille.segment.BrailleSegment 1 line, 0 headings, 40 cols> 1800 1801 >>> print(allSegments[0]) 1802 ---begin segment--- 1803 <music21.braille.segment BrailleSegment> 1804 Measure 1, Signature Grouping 1: 1805 <music21.meter.TimeSignature 3/4> 1806 === 1807 Measure 1, Note Grouping 1: 1808 <music21.clef.TrebleClef> 1809 <music21.note.Note C> 1810 <music21.note.Note C> 1811 <music21.note.Note C> 1812 === 1813 Measure 2, Note Grouping 1: 1814 <music21.note.Note E> 1815 === 1816 ---end segment--- 1817 1818 >>> allSegments[1] 1819 <music21.braille.segment.BrailleSegment 1 line, 0 headings, 40 cols> 1820 1821 >>> print(allSegments[1]) 1822 ---begin segment--- 1823 <music21.braille.segment BrailleSegment> 1824 Measure 2, Note Grouping 2: 1825 <music21.note.Note E> 1826 <music21.note.Note E> 1827 === 1828 Measure 3, Note Grouping 1: 1829 <music21.note.Note G> 1830 <music21.note.Note G> 1831 <music21.note.Note G> 1832 === 1833 Measure 4, Note Grouping 1: 1834 <music21.note.Note C> 1835 <music21.bar.Barline type=final> 1836 === 1837 ---end segment--- 1838 1839 If we insert an optional division, the division 1840 only appears if there are 48 elements in the current segment: 1841 1842 >>> tnC = copy.deepcopy(tn) 1843 >>> tnC.measure(1).insert(1.0, braille.objects.BrailleOptionalSegmentDivision()) 1844 >>> allSegments = segment.getRawSegments(tnC) 1845 >>> len(allSegments) 1846 1 1847 1848 If by happenstance a segment division object is encountered where a division 1849 has just been created (or the very beginning), 1850 no unnecessary empty segment will be created: 1851 1852 >>> tnD = copy.deepcopy(tn) 1853 >>> tnD.measure(1).insert(0, braille.objects.BrailleSegmentDivision()) 1854 >>> allSegments = segment.getRawSegments(tnD) 1855 >>> len(allSegments) 1856 1 1857 ''' 1858 allSegments = [] 1859 1860 currentSegment = BrailleSegment(lineLength=maxLineLength) 1861 1862 elementsInCurrentSegment: int = 0 1863 1864 startANewSegment: bool = False 1865 1866 # TODO: why is this skipping the measure layer and getting voices? 1867 for music21Measure in music21Part.getElementsByClass([stream.Measure, stream.Voice]): 1868 prepareBeamedNotes(music21Measure) 1869 brailleElements = extractBrailleElements(music21Measure) 1870 ordinal: int = 0 1871 previousAffinityCode = Affinity._LOWEST # -1 1872 1873 for brailleElement in brailleElements: 1874 if startANewSegment: 1875 # Dispose of existing segment 1876 if brailleElement.offset != 0.0: 1877 # Correct use of end hyphen depends on knowledge of line length (Ex. 10-5) 1878 # So just be conservative and add it 1879 currentSegment.endHyphen = True 1880 1881 # Start new segment and increment number if this is a midmeasure segment start 1882 allSegments.append(currentSegment) 1883 currentSegment = BrailleSegment(lineLength=maxLineLength) 1884 if brailleElement.offset != 0.0: 1885 currentSegment.beginsMidMeasure = True 1886 1887 elementsInCurrentSegment = 0 1888 if previousAffinityCode is not Affinity._LOWEST: 1889 ordinal += 1 1890 1891 startANewSegment = False 1892 if 'BrailleSegmentDivision' in brailleElement.classes: 1893 if ('BrailleOptionalSegmentDivision' in brailleElement.classes 1894 and elementsInCurrentSegment <= MAX_ELEMENTS_IN_SEGMENT): 1895 # Optional condition not met -- pass 1896 pass 1897 # Optional condition met, or required 1898 elif elementsInCurrentSegment > 0: 1899 startANewSegment = True 1900 # All cases: continue, so we don't add anything to the segment 1901 continue 1902 elif ( 1903 isinstance(brailleElement, bar.Barline) 1904 and elementsInCurrentSegment > MAX_ELEMENTS_IN_SEGMENT 1905 and brailleElement.type in ('double', 'final') 1906 ): 1907 # see test_drill10_2 1908 startANewSegment = True 1909 # execute the block below to ensure barline is added to current segment 1910 1911 if brailleElement.affinityCode < previousAffinityCode: 1912 ordinal += 1 1913 1914 affinityCode = brailleElement.affinityCode 1915 if affinityCode == Affinity.SPLIT1_NOTEGROUP: 1916 affinityCode = Affinity.INACCORD 1917 elif affinityCode == Affinity.SPLIT2_NOTEGROUP: 1918 affinityCode = Affinity.NOTEGROUP 1919 1920 segmentKey = SegmentKey(music21Measure.number, 1921 ordinal, 1922 affinityCode, 1923 setHand 1924 ) 1925 if segmentKey not in currentSegment: 1926 currentSegment[segmentKey] = BrailleElementGrouping() 1927 brailleElementGrouping = currentSegment[segmentKey] 1928 brailleElementGrouping.append(brailleElement) 1929 elementsInCurrentSegment += 1 1930 1931 # NOT variable affinityCode! 1932 previousAffinityCode = brailleElement.affinityCode 1933 allSegments.append(currentSegment) 1934 return allSegments 1935 1936 1937def extractBrailleElements(music21Measure): 1938 ''' 1939 Takes in a :class:`~music21.stream.Measure` and returns a 1940 :class:`~music21.braille.segment.BrailleElementGrouping` of correctly ordered 1941 :class:`~music21.base.Music21Object` instances which can be directly transcribed to 1942 braille. 1943 1944 >>> from music21.braille import segment 1945 >>> tn = converter.parse('tinynotation: 2/4 c16 c c c d d d d', makeNotation=False) 1946 >>> tn = tn.makeNotation(cautionaryNotImmediateRepeat=False) 1947 >>> measure = tn[0] 1948 >>> measure.append(spanner.Slur(measure.notes[0],measure.notes[-1])) 1949 >>> measure.show('text') 1950 {0.0} <music21.clef.TrebleClef> 1951 {0.0} <music21.meter.TimeSignature 2/4> 1952 {0.0} <music21.note.Note C> 1953 {0.25} <music21.note.Note C> 1954 {0.5} <music21.note.Note C> 1955 {0.75} <music21.note.Note C> 1956 {1.0} <music21.note.Note D> 1957 {1.25} <music21.note.Note D> 1958 {1.5} <music21.note.Note D> 1959 {1.75} <music21.note.Note D> 1960 {2.0} <music21.spanner.Slur <music21.note.Note C><music21.note.Note D>> 1961 {2.0} <music21.bar.Barline type=final> 1962 1963 1964 Spanners are dealt with in :func:`~music21.braille.segment.prepareSlurredNotes`, 1965 so they are not returned by this function, as seen below. 1966 1967 >>> print(segment.extractBrailleElements(measure)) 1968 <music21.meter.TimeSignature 2/4> 1969 <music21.clef.TrebleClef> 1970 <music21.note.Note C> 1971 <music21.note.Note C> 1972 <music21.note.Note C> 1973 <music21.note.Note C> 1974 <music21.note.Note D> 1975 <music21.note.Note D> 1976 <music21.note.Note D> 1977 <music21.note.Note D> 1978 <music21.bar.Barline type=final> 1979 ''' 1980 allElements = BrailleElementGrouping() 1981 for music21Object in music21Measure: 1982 try: 1983 if isinstance(music21Object, bar.Barline): 1984 if music21Object.type == 'regular': 1985 continue 1986 setAffinityCode(music21Object) 1987 music21Object.editorial.brailleEnglish = [str(music21Object)] 1988 allElements.append(music21Object) 1989 except BrailleSegmentException as notSupportedException: # pragma: no cover 1990 isExempt = [isinstance(music21Object, music21Class) 1991 for music21Class in excludeFromBrailleElements] 1992 if isExempt.count(True) == 0: 1993 environRules.warn(f'{notSupportedException}') 1994 1995 allElements.sort(key=lambda x: (x.offset, x.classSortOrder)) 1996 if len(allElements) >= 2 and isinstance(allElements[-1], dynamics.Dynamic): 1997 if isinstance(allElements[-2], bar.Barline): 1998 allElements[-1].classSortOrder = -1 1999 allElements.sort(key=lambda x: (x.offset, x.classSortOrder)) 2000 2001 return allElements 2002 2003 2004def prepareBeamedNotes(music21Measure): 2005 ''' 2006 Takes in a :class:`~music21.stream.Measure` and labels beamed notes 2007 of smaller value than an 8th with beamStart and beamContinue keywords 2008 in accordance with beaming rules in braille music. 2009 2010 A more in-depth explanation of beaming in braille can be found in 2011 Chapter 15 of Introduction to Braille Music Transcription, Second 2012 Edition, by Mary Turner De Garmo. 2013 2014 >>> from music21.braille import segment 2015 >>> tn = converter.parse('tinynotation: 2/4 c16 c c c d d d d') 2016 >>> tn = tn.makeNotation(cautionaryNotImmediateRepeat=False) 2017 >>> tn.show('text') 2018 {0.0} <music21.stream.Measure 1 offset=0.0> 2019 {0.0} <music21.clef.TrebleClef> 2020 {0.0} <music21.meter.TimeSignature 2/4> 2021 {0.0} <music21.note.Note C> 2022 {0.25} <music21.note.Note C> 2023 {0.5} <music21.note.Note C> 2024 {0.75} <music21.note.Note C> 2025 {1.0} <music21.note.Note D> 2026 {1.25} <music21.note.Note D> 2027 {1.5} <music21.note.Note D> 2028 {1.75} <music21.note.Note D> 2029 {2.0} <music21.bar.Barline type=final> 2030 >>> measure = tn[0] 2031 >>> segment.prepareBeamedNotes(measure) 2032 >>> measure.notes[0].beamStart 2033 True 2034 >>> measure.notes[1].beamContinue 2035 True 2036 >>> measure.notes[2].beamContinue 2037 True 2038 >>> measure.notes[3].beamContinue 2039 True 2040 ''' 2041 allNotes = music21Measure.notes.stream() 2042 2043 for sampleNote in allNotes: 2044 sampleNote.beamStart = False 2045 sampleNote.beamContinue = False 2046 allNotesAndRests = music21Measure.notesAndRests.stream() 2047 2048 def withBeamFilter(el, unused): 2049 return (el.beams is not None) and len(el.beams) > 0 2050 2051 def beamStartFilter(el, unused): 2052 return el.beams.getByNumber(1).type == 'start' 2053 2054 def beamStopFilter(el, unused): 2055 return el.beams.getByNumber(1).type == 'stop' 2056 2057 allStartIter = allNotes.iter().addFilter(withBeamFilter).addFilter(beamStartFilter) 2058 allStopIter = allNotes.iter().addFilter(withBeamFilter).addFilter(beamStopFilter) 2059 2060 if len(allStartIter) != len(allStopIter): 2061 environRules.warn('Incorrect beaming: number of start notes != to number of stop notes.') 2062 return 2063 2064 for beamIndex, startNote in enumerate(allStartIter): 2065 # Eighth notes cannot be beamed in braille (redundant, because beamed 2066 # notes look like eighth notes, but nevertheless useful). 2067 if startNote.quarterLength == 0.5: 2068 continue 2069 2070 stopNote = allStopIter[beamIndex] 2071 startIndex = allNotesAndRests.index(startNote) 2072 stopIndex = allNotesAndRests.index(stopNote) 2073 2074 delta = stopIndex - startIndex + 1 2075 if delta < 3: # 2. The group must be composed of at least three notes. 2076 continue 2077 # 1. All notes in the group must have precisely the same value. 2078 # 3. A rest of the same value may take the place of the first note in a group, 2079 # but if the rest is located anywhere else, grouping may not be used. 2080 allNotesOfSameValue = True 2081 for noteIndex in range(startIndex + 1, stopIndex + 1): 2082 if (allNotesAndRests[noteIndex].quarterLength != startNote.quarterLength 2083 or isinstance(allNotesAndRests[noteIndex], note.Rest)): 2084 allNotesOfSameValue = False 2085 break 2086 try: 2087 afterStopNote = allNotesAndRests[stopIndex + 1] 2088 if (isinstance(afterStopNote, note.Rest) 2089 and (int(afterStopNote.beat) == int(stopNote.beat))): 2090 allNotesOfSameValue = False 2091 except IndexError: # stopNote is last note of measure. 2092 pass 2093 if not allNotesOfSameValue: 2094 continue 2095 try: 2096 # 4. If the notes in the group are followed immediately by a 2097 # true eighth note or by an eighth rest, 2098 # grouping may not be used, unless the eighth is located in a new measure. 2099 if allNotesAndRests[stopIndex + 1].quarterLength == 0.5: 2100 continue 2101 except IndexError: # stopNote is last note of measure. 2102 pass 2103 2104 startNote.beamStart = True 2105 try: 2106 beforeStartNote = allNotesAndRests[startIndex - 1] 2107 if (isinstance(beforeStartNote, note.Rest) 2108 and int(beforeStartNote.beat) == int(startNote.beat) 2109 and beforeStartNote.quarterLength == startNote.quarterLength): 2110 startNote.beamContinue = True 2111 except IndexError: # startNote is first note of measure. 2112 pass 2113 for noteIndex in range(startIndex + 1, stopIndex + 1): 2114 allNotesAndRests[noteIndex].beamContinue = True 2115 2116 2117def setAffinityCode(music21Object): 2118 ''' 2119 Takes in a :class:`~music21.base.Music21Object`, and does two things: 2120 2121 * Modifies the :attr:`~music21.base.Music21Object.classSortOrder` attribute of the 2122 object to fit the slightly modified ordering of objects in braille music. 2123 2124 * Adds an affinity code to the object. This code indicates which surrounding 2125 objects the object should be grouped with. 2126 2127 2128 A BrailleSegmentException is raised if an affinity code cannot be assigned to 2129 the object. 2130 2131 2132 As seen in the following example, the affinity code of a :class:`~music21.note.Note` 2133 and a :class:`~music21.clef.TrebleClef` are the same, because they should be grouped 2134 together. However, the classSortOrder indicates that the TrebleClef should come first 2135 in the braille. 2136 2137 >>> n1 = note.Note('D5') 2138 >>> braille.segment.setAffinityCode(n1) 2139 >>> n1.affinityCode 2140 <Affinity.NOTEGROUP: 9> 2141 >>> n1.classSortOrder 2142 10 2143 >>> c1 = clef.TrebleClef() 2144 >>> braille.segment.setAffinityCode(c1) 2145 >>> c1.affinityCode 2146 <Affinity.NOTEGROUP: 9> 2147 >>> c1.classSortOrder 2148 7 2149 ''' 2150 for (music21Class, code, sortOrder) in affinityCodes: 2151 if isinstance(music21Object, music21Class): 2152 music21Object.affinityCode = code 2153 music21Object.classSortOrder = sortOrder 2154 return 2155 2156 if isinstance(music21Object, expressions.TextExpression): 2157 music21Object.affinityCode = Affinity.NOTEGROUP 2158 if len(music21Object.content.split()) > 1: 2159 music21Object.affinityCode = Affinity.LONG_TEXTEXPR 2160 music21Object.classSortOrder = 8 2161 return 2162 2163 if isinstance(music21Object, BrailleTranscriptionHelper): 2164 return 2165 2166 raise BrailleSegmentException(f'{music21Object} cannot be transcribed to braille.') 2167 2168 2169def areGroupingsIdentical(noteGroupingA, noteGroupingB): 2170 ''' 2171 Takes in two note groupings, noteGroupingA and noteGroupingB. Returns True 2172 if both groupings have identical contents. False otherwise. 2173 2174 Helper for numRepeats... 2175 2176 Needs two identical length groupings. 2177 2178 >>> a = [note.Note('C4'), note.Note('D4')] 2179 >>> b = [note.Note('C4'), note.Note('D4')] 2180 >>> braille.segment.areGroupingsIdentical(a, b) 2181 True 2182 2183 >>> d = b.pop() 2184 >>> braille.segment.areGroupingsIdentical(a, b) 2185 False 2186 >>> c = [note.Rest(), note.Note('D4')] 2187 >>> braille.segment.areGroupingsIdentical(a, c) 2188 False 2189 ''' 2190 if len(noteGroupingA) == len(noteGroupingB): 2191 for (elementA, elementB) in zip(noteGroupingA, noteGroupingB): 2192 if elementA != elementB: 2193 return False 2194 return True 2195 return False 2196 2197 2198# ------------------------------------------------------------------------------ 2199# Helper Methods 2200 2201def splitNoteGrouping(noteGrouping, beatDivisionOffset=0): 2202 ''' 2203 Almost identical to :func:`~music21.braille.segment.splitMeasure`, but 2204 functions on a :class:`~music21.braille.segment.BrailleElementGrouping` 2205 instead. 2206 2207 >>> from music21.braille import segment 2208 >>> bg = segment.BrailleElementGrouping() 2209 >>> bg.timeSignature = meter.TimeSignature('2/2') 2210 >>> s = converter.parse('tinyNotation: 2/2 c4 d r e') 2211 >>> for n in s.recurse().notesAndRests: 2212 ... bg.append(n) 2213 >>> left, right = segment.splitNoteGrouping(bg) 2214 >>> left 2215 <music21.braille.segment.BrailleElementGrouping 2216 [<music21.note.Note C>, <music21.note.Note D>]> 2217 2218 >>> print(left) 2219 <music21.note.Note C> 2220 <music21.note.Note D> 2221 2222 >>> right 2223 <music21.braille.segment.BrailleElementGrouping 2224 [<music21.note.Rest quarter>, <music21.note.Note E>]> 2225 2226 2227 Now split one beat division earlier than it should be. For 2/2 that means 2228 one half of a beat, or one quarter note earlier: 2229 2230 >>> left, right = segment.splitNoteGrouping(bg, beatDivisionOffset=1) 2231 >>> left 2232 <music21.braille.segment.BrailleElementGrouping 2233 [<music21.note.Note C>]> 2234 >>> right 2235 <music21.braille.segment.BrailleElementGrouping 2236 [<music21.note.Note D>, <music21.note.Rest quarter>, <music21.note.Note E>]> 2237 ''' 2238 music21Measure = stream.Measure() 2239 for brailleElement in noteGrouping: 2240 music21Measure.insert(brailleElement.offset, brailleElement) 2241 (leftMeasure, rightMeasure) = splitMeasure(music21Measure, 2242 beatDivisionOffset, 2243 noteGrouping.timeSignature) 2244 leftBrailleElements = copy.copy(noteGrouping) 2245 leftBrailleElements.internalList = [] 2246 for brailleElement in leftMeasure: 2247 leftBrailleElements.append(brailleElement) 2248 2249 rightBrailleElements = copy.copy(noteGrouping) 2250 rightBrailleElements.internalList = [] 2251 for brailleElement in rightMeasure: 2252 rightBrailleElements.append(brailleElement) 2253 2254 return leftBrailleElements, rightBrailleElements 2255 2256 2257def splitMeasure(music21Measure, beatDivisionOffset=0, useTimeSignature=None): 2258 ''' 2259 Takes a :class:`~music21.stream.Measure`, divides it in two parts, and returns a 2260 two-tuple of (leftMeasure, rightMeasure). The parameters are as 2261 follows: 2262 2263 * beatDivisionOffset => Adjusts the end offset of the first partition by a certain amount 2264 of beats to the left. 2265 * useTimeSignature => In the event that the Measure comes from the middle of a Part 2266 and thus does not define an explicit :class:`~music21.meter.TimeSignature`. If not 2267 provided, a TimeSignature is retrieved by 2268 using :meth:`~music21.stream.Measure.bestTimeSignature`. 2269 2270 >>> m = stream.Measure() 2271 >>> m.append(note.Note('C4')) 2272 >>> m.append(note.Note('D4')) 2273 >>> left, right = braille.segment.splitMeasure(m) 2274 >>> left.show('text') 2275 {0.0} <music21.note.Note C> 2276 >>> right.show('text') 2277 {1.0} <music21.note.Note D> 2278 ''' 2279 if useTimeSignature is not None: 2280 ts = useTimeSignature 2281 else: 2282 ts = music21Measure.bestTimeSignature() 2283 2284 offset = 0.0 2285 if beatDivisionOffset != 0: 2286 if abs(beatDivisionOffset) > len(ts.beatDivisionDurations): 2287 raise BrailleSegmentException( 2288 f'beatDivisionOffset {beatDivisionOffset} is outside ' 2289 + f'of ts.beatDivisionDurations {ts.beatDivisionDurations}' 2290 ) 2291 duration_index = len(ts.beatDivisionDurations) - abs(beatDivisionOffset) 2292 try: 2293 offset += opFrac(ts.beatDivisionDurations[duration_index].quarterLength) 2294 offset = opFrac(offset) 2295 except IndexError: 2296 environRules.warn('Problem in converting a time signature in measure ' 2297 + f'{music21Measure.number}, offset may be wrong') 2298 bs = copy.deepcopy(ts.beatSequence) 2299 2300 numberOfPartitions = 2 2301 try: 2302 bs.partitionByCount(numberOfPartitions, loadDefault=False) 2303 (startOffsetZero, endOffsetZero) = bs.getLevelSpan()[0] 2304 except meter.MeterException: 2305 numberOfPartitions += 1 2306 bs.partitionByCount(numberOfPartitions, loadDefault=False) 2307 startOffsetZero = bs.getLevelSpan()[0][0] 2308 endOffsetZero = bs.getLevelSpan()[-2][-1] 2309 endOffsetZero -= offset 2310 2311 leftMeasure = stream.Measure() 2312 rightMeasure = stream.Measure() 2313 for x in music21Measure: 2314 if (x.offset >= startOffsetZero 2315 and (x.offset < endOffsetZero 2316 or (x.offset == endOffsetZero 2317 and isinstance(x, bar.Barline)))): 2318 leftMeasure.insert(x.offset, x) 2319 else: 2320 rightMeasure.insert(x.offset, x) 2321 for n in rightMeasure.notes: 2322 if n.tie is not None: 2323 leftMeasure.append(n) 2324 rightMeasure.remove(n) 2325 endOffsetZero += n.duration.quarterLength 2326 continue 2327 break 2328 2329 rest0Length = music21Measure.duration.quarterLength - endOffsetZero 2330 r0 = note.Rest(quarterLength=rest0Length) 2331 leftMeasure.insert(endOffsetZero, r0) 2332 2333 r1 = note.Rest(quarterLength=endOffsetZero) 2334 rightMeasure.insert(0.0, r1) 2335 2336 ts0_delete = False 2337 if leftMeasure.timeSignature is None: 2338 ts0_delete = True 2339 leftMeasure.timeSignature = ts 2340 rightMeasure.timeSignature = ts 2341 leftMeasure.mergeAttributes(music21Measure) 2342 rightMeasure.mergeAttributes(music21Measure) 2343 leftMeasure.makeBeams(inPlace=True) 2344 rightMeasure.makeBeams(inPlace=True) 2345 prepareBeamedNotes(leftMeasure) 2346 prepareBeamedNotes(rightMeasure) 2347 leftMeasure.remove(r0) 2348 rightMeasure.remove(r1) 2349 if ts0_delete: 2350 leftMeasure.remove(ts) 2351 rightMeasure.remove(ts) 2352 return (leftMeasure, rightMeasure) 2353 2354 2355# ------------------------------------------------------------------------------ 2356 2357 2358class Test(unittest.TestCase): 2359 2360 def testGetRawSegments(self): 2361 from music21 import converter 2362 2363 tn = converter.parse("tinynotation: 3/4 c4 c c e e e g g g c'2.") 2364 tn = tn.makeNotation(cautionaryNotImmediateRepeat=False) 2365 2366 rawSegList = getRawSegments(tn) 2367 unused = str(rawSegList[0]) 2368 2369 2370if __name__ == '__main__': 2371 import music21 2372 music21.mainTest(Test) # , runTest='testGetRawSegments') 2373 2374