Repository: cuthbertLab/music21
Pull request #1162 (2021-11-07 19:08:19)

Clean up temporary attributes set by braille translator
Fixes side-effects on streams left by braille translator, and removes related workarounds in test suite.

Simple fix for now without committing to any refactoring.


Patch:
diff --git a/music21/braille/basic.py b/music21/braille/basic.py
index b979b1a15..29425c18e 100644
--- a/music21/braille/basic.py
+++ b/music21/braille/basic.py
@@ -33,6 +33,17 @@ symbols = lookup.symbols
 environRules = environment.Environment('basic.py')
 
 beamStatus = {}
+
+# Attributes that the translator currently sets on Music21Objects
+# that should be cleaned up after transcription
+TEMPORARY_ATTRIBUTES = ['beginLongBracketSlur',
+                        'endLongBracketSlur',
+                        'beginLongDoubleSlur',
+                        'endLongDoubleSlur',
+                        'shortSlur',
+                        'beamStart',
+                        'beamContinue']
+
 # ------------------------------------------------------------------------------
 # music21Object to braille unicode methods
 
@@ -648,15 +659,8 @@ def noteToBraille(
     # Note: beamStatus is a helper that I hope to remove
     # when moving all the translation features to a separate class.
     music21Note.editorial.brailleEnglish = []
-    falseKeywords = ['beginLongBracketSlur',
-                     'endLongBracketSlur',
-                     'beginLongDoubleSlur',
-                     'endLongDoubleSlur',
-                     'shortSlur',
-                     'beamStart',
-                     'beamContinue']
-
-    for keyword in falseKeywords:
+
+    for keyword in TEMPORARY_ATTRIBUTES:
         try:
             beamStatus[keyword] = getattr(music21Note, keyword)
         except AttributeError:
diff --git a/music21/braille/segment.py b/music21/braille/segment.py
index f66ba7090..84a45b47c 100644
--- a/music21/braille/segment.py
+++ b/music21/braille/segment.py
@@ -479,6 +479,16 @@ class BrailleSegment(text.BrailleText):
 
         return self.brailleText
 
+    def _cleanupAttributes(self, noteGrouping):
+        '''
+        Removes temporary attributes from Music21Objects set during transcription.
+        Run this only AFTER any possible re-transcription in extractNoteGrouping().
+        '''
+        for el in noteGrouping:
+            for kw in basic.TEMPORARY_ATTRIBUTES:
+                if hasattr(el, kw):
+                    delattr(el, kw)
+
     def addDummyRests(self):
         '''
         Adds as many dummy rests as self.dummyRestLength to the signatures of
@@ -821,6 +831,7 @@ class BrailleSegment(text.BrailleText):
                 self.addToNewLine(brailleNoteGrouping)
 
         self.addRepeatSymbols(noteGrouping.numRepeats)
+        self._cleanupAttributes(noteGrouping)
 
     def addRepeatSymbols(self, repeatTimes):
         '''
@@ -1317,9 +1328,12 @@ class BrailleGrandSegment(BrailleSegment, text.BrailleKeyboard):
                     noteGrouping.showClefSigns = inaccords.showClefSigns
                     noteGrouping.upperFirstInNoteFingering = inaccords.upperFirstInNoteFingering
                     voice_trans.append(ngMod.transcribeNoteGrouping(noteGrouping))
+                    self._cleanupAttributes(noteGrouping)
                 brailleStr = symbols['full_inaccord'].join(voice_trans)
             elif rightOrLeftKey is not None:
-                brailleStr = ngMod.transcribeNoteGrouping(self._groupingDict.get(rightOrLeftKey))
+                noteGrouping = self._groupingDict.get(rightOrLeftKey)
+                brailleStr = ngMod.transcribeNoteGrouping(noteGrouping)
+                self._cleanupAttributes(noteGrouping)
             else:
                 brailleStr = ''
 
diff --git a/music21/musicxml/m21ToXml.py b/music21/musicxml/m21ToXml.py
index 90973ce6f..8989f75c1 100644
--- a/music21/musicxml/m21ToXml.py
+++ b/music21/musicxml/m21ToXml.py
@@ -3508,8 +3508,8 @@ class MeasureExporter(XMLExporterBase):
             chordOrN = n
         else:
             chordOrN = chordParent
-            # Ensure style is read from `n` before reading from `chordOrN`
-            self.setPrintStyle(mxNote, n)  # sets color
+            # Ensure color is read from `n`, since only `chordOrN` is handled below
+            self.setColor(mxNote, n)
 
         # self.setFont(mxNote, chordOrN)
         self.setPrintStyle(mxNote, chordOrN)  # sets color
@@ -3912,18 +3912,17 @@ class MeasureExporter(XMLExporterBase):
         </note>
 
 
-        Test that notehead and style translation works:
+        Test that notehead translation works:
 
         >>> g = pitch.Pitch('g3')
         >>> h = note.Note('b4')
         >>> h.notehead = 'diamond'
         >>> h.style.color = 'gold'
-        >>> h.style.absoluteX = 176
         >>> ch2 = chord.Chord([g, h])
         >>> ch2.quarterLength = 2.0
         >>> mxNoteList = MEX.chordToXml(ch2)
         >>> MEX.dump(mxNoteList[1])
-        <note color="#FFD700" default-x="176">
+        <note color="#FFD700">
           <chord />
           <pitch>
             <step>B</step>


Test patch:
diff --git a/music21/braille/test.py b/music21/braille/test.py
index 0612b0b1d..f1075c219 100644
--- a/music21/braille/test.py
+++ b/music21/braille/test.py
@@ -2506,17 +2506,12 @@ Barline final ⠣⠅
         m[2].append(spanner.Slur(m[0].notes[0], m[2].notes[0]))
         m[3].append(spanner.Slur(m[2].notes[0], m[3].notes[0]))
         m[3].rightBarline = None
-
-        # need two copies because of a bug...
-        import copy
-        bmm = copy.deepcopy(bm)
         self.s = bm
         self.methodArgs = {'showFirstMeasureNumber': False}
         self.b = '''
         ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠩⠩⠼⠉⠲⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
         ⠰⠃⠐⠎⠺⠀⠊⠨⠛⠋⠑⠙⠚⠀⠰⠃⠘⠆⠪⠚⠙⠑⠋⠀⠟⠄⠘⠆
         '''
-        self.s = bmm
         self.methodArgs = {'showFirstMeasureNumber': False, 'slurLongPhraseWithBrackets': False}
         self.b = '''
         ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠩⠩⠼⠉⠲⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
@@ -2549,15 +2544,12 @@ Barline final ⠣⠅
         m = bm.getElementsByClass('Measure')
         m[-1].append(spanner.Slur(m[0].notes[0], m[-1].notes[-1]))
         m[-1].rightBarline = None
-        import copy
-        bmm = copy.deepcopy(bm)
         self.s = bm
         self.methodArgs = {'showFirstMeasureNumber': False}
         self.b = '''
         ⠀⠀⠀⠀⠀⠀⠣⠼⠉⠲⠀⠀⠀⠀⠀⠀
         ⠰⠃⠨⠟⠄⠈⠉⠀⠛⠙⠑⠙⠚⠊⠘⠆
         '''
-        self.s = bmm
         self.methodArgs = {'showFirstMeasureNumber': False, 'slurLongPhraseWithBrackets': False}
         self.b = '''
         ⠀⠀⠀⠀⠀⠀⠣⠼⠉⠲⠀⠀⠀⠀⠀
@@ -2571,15 +2563,12 @@ Barline final ⠣⠅
         ml = bm.getElementsByClass('Measure').last()
         ml.append(spanner.Slur(ml.notes.first(), ml.notes.last()))
         ml.rightBarline = None
-        import copy
-        bmm = copy.deepcopy(bm)
         self.s = bm
         self.methodArgs = {'showFirstMeasureNumber': False}
         self.b = '''
         ⠀⠀⠀⠀⠀⠀⠣⠼⠉⠲⠀⠀⠀⠀⠀⠀
         ⠨⠟⠄⠈⠉⠀⠰⠃⠛⠙⠑⠙⠚⠊⠘⠆
         '''
-        self.s = bmm
         self.methodArgs = {'showFirstMeasureNumber': False, 'slurLongPhraseWithBrackets': False}
         self.b = '''
         ⠀⠀⠀⠀⠀⠀⠣⠼⠉⠲⠀⠀⠀⠀⠀
@@ -2592,15 +2581,12 @@ Barline final ⠣⠅
         m = bm.getElementsByClass('Measure')
         m[-1].append(spanner.Slur(m[0].notes[0], m[-1].notes[-1]))
         m[-1].rightBarline = None
-        import copy
-        bmm = copy.deepcopy(bm)
         self.s = bm
         self.methodArgs = {'showFirstMeasureNumber': False}
         self.b = '''
         ⠀⠀⠀⠀⠀⠀⠀⠀⠼⠉⠲⠀⠀⠀⠀⠀⠀⠀⠀⠀
         ⠰⠃⠨⠋⠛⠓⠛⠋⠑⠀⠝⠄⠈⠉⠀⠹⠘⠆⠧⠧
         '''
-        self.s = bmm
         self.methodArgs = {'showFirstMeasureNumber': False, 'slurLongPhraseWithBrackets': False}
         self.b = '''
         ⠀⠀⠀⠀⠀⠀⠀⠀⠼⠉⠲⠀⠀⠀⠀⠀⠀⠀⠀
@@ -2613,15 +2599,11 @@ Barline final ⠣⠅
         m = bm.getElementsByClass('Measure')
         m[1].append(spanner.Slur(m[0].notes[0], m[1].notes[0]))
         m[-1].rightBarline = None
-        import copy
-        bmm = copy.deepcopy(bm)
-        self.s = bm
         self.methodArgs = {'showFirstMeasureNumber': False}
         self.b = '''
         ⠀⠀⠀⠀⠀⠀⠀⠀⠼⠉⠲⠀⠀⠀⠀⠀⠀⠀⠀⠀
         ⠰⠃⠨⠋⠛⠓⠛⠋⠑⠀⠝⠄⠘⠆⠈⠉⠀⠹⠧⠧
         '''
-        self.s = bmm
         self.methodArgs = {'showFirstMeasureNumber': False, 'slurLongPhraseWithBrackets': False}
         self.b = '''
         ⠀⠀⠀⠀⠀⠀⠀⠀⠼⠉⠲⠀⠀⠀⠀⠀⠀⠀⠀