Repository: cuthbertLab/music21
Pull request #836 (2021-02-08 01:16:01)

Improve braille segment divisions
Fixes:
 - IndexError when a BrailleSegmentDivision is found at a location where the segment is already empty (such as the beginning of a stream, or another segment break from a double barline)
 - Segment breaks weren't being created from double barlines after 48 elements (element count variable kept being cleared every measure)
 - Measure element group numbers weren't being incremented after segment breaks
 
Tests that had to be adjusted:
- `test_drill10_2` now shows a segment break being created correctly from a double barline after 48 elements
- `test_example11_2` now shows element group numbers being incremented

See the test changes by looking at the diff on the original English at b4625f56698858ce9b64ccf4fb509fe53bb7a395

coveralls: 
[![Coverage Status](https://coveralls.io/builds/36938707/badge)](https://coveralls.io/builds/36938707)

Coverage decreased (-0.005%) to 91.498% when pulling **b886a822d47e4ff53f387bc9832058eaf24cca9e on jacobtylerwalls:segment-divisions-safer** into **f42a7e9e8fb19719db6e3109032b611febdc02ca on cuthbertLab:master**.


Patch:
diff --git a/music21/braille/segment.py b/music21/braille/segment.py
index 884194c02..f7fcbcda4 100644
--- a/music21/braille/segment.py
+++ b/music21/braille/segment.py
@@ -1467,7 +1467,7 @@ def findSegments(music21Part, **partKeywords):
     >>> print(str(allSegments[1]))
     ---begin segment---
     <music21.braille.segment BrailleSegment>
-    Measure 8, Note Grouping 1:
+    Measure 8, Note Grouping 2:
     <music21.note.Note G>
     ===
     Measure 9, Note Grouping 1:
@@ -1722,14 +1722,15 @@ def prepareSlurredNotes(music21Part, **keywords):
 
 def getRawSegments(music21Part, setHand=None):
     '''
-    Takes in a :class:`~music21.stream.Part` divided it up into segments (i.e. instances of
+    Takes in a :class:`~music21.stream.Part`, divides it up into segments (i.e. instances of
     :class:`~music21.braille.segment.BrailleSegment`). This method assumes
     that the Part is already divided up into measures
     (see :class:`~music21.stream.Measure`). An acceptable input is shown below.
 
-    This will automatically find appropriate segment breaks at braille.objects.BrailleSegmentBreak
-    or braille.objects.BrailleOptionalSegmentBreak or after 48 elements if a double bar or
-    repeat sign is encountered.
+    This will automatically find appropriate segment breaks at
+    :class:`~music21.braille.objects.BrailleSegmentDivision`
+    or :class:`~music21.braille.objects.BrailleOptionalSegmentDivision`
+    or after 48 elements if a double bar or repeat sign is encountered.
 
     Two methods are called on each measure during the creation of segments:
 
@@ -1832,7 +1833,7 @@ def getRawSegments(music21Part, setHand=None):
     >>> print(allSegments[1])
     ---begin segment---
     <music21.braille.segment BrailleSegment>
-    Measure 2, Note Grouping 1:
+    Measure 2, Note Grouping 2:
     <music21.note.Note E>
     <music21.note.Note E>
     ===
@@ -1846,50 +1847,81 @@ def getRawSegments(music21Part, setHand=None):
     <music21.bar.Barline type=final>
     ===
     ---end segment---
+
+    If we insert an optional division, the division
+    only appears if there are 48 elements in the current segment:
+
+    >>> tnC = copy.deepcopy(tn)
+    >>> tnC.measure(1).insert(1.0, braille.objects.BrailleOptionalSegmentDivision())
+    >>> allSegments = segment.getRawSegments(tnC)
+    >>> len(allSegments)
+    1
+
+    If by happenstance a segment division object is encountered where a division
+    has just been created (or the very beginning),
+    no unnecessary empty segment will be created:
+
+    >>> tnD = copy.deepcopy(tn)
+    >>> tnD.measure(1).insert(0, braille.objects.BrailleSegmentDivision())
+    >>> allSegments = segment.getRawSegments(tnD)
+    >>> len(allSegments)
+    1
     '''
     allSegments = []
 
     currentSegment = BrailleSegment()
 
+    elementsInCurrentSegment: int = 0
+
+    startANewSegment: bool = False
+
+    # TODO: why is this skipping the measure layer and getting voices?
     for music21Measure in music21Part.getElementsByClass([stream.Measure, stream.Voice]):
         prepareBeamedNotes(music21Measure)
         brailleElements = extractBrailleElements(music21Measure)
-        elementsInCurrentSegment = 0
-
-        offsetFactor = 0
+        ordinal: int = 0
         previousAffinityCode = Affinity._LOWEST  # -1
-        for brailleElement in brailleElements:
-            # TODO: use objects.BrailleSegmentDivision() here...
-            startANewSegment = False
-            if 'BrailleOptionalSegmentDivision' in brailleElement.classes:
-                # do not factor these two ifs into one, so that we fall through
-                # 'BrailleSegmentDivision' of which this is a subclass...
-                if elementsInCurrentSegment > MAX_ELEMENTS_IN_SEGMENT:
-                    startANewSegment = True
-            elif 'BrailleSegmentDivision' in brailleElement.classes:
-                startANewSegment = True
-            elif 'Barline' in brailleElement.classes:
-                if (elementsInCurrentSegment > MAX_ELEMENTS_IN_SEGMENT
-                        and brailleElement.type in ('double', 'final')):
-                    startANewSegment = True
 
+        for brailleElement in brailleElements:
             if startANewSegment:
-                # end of segment, get new one...
+                # Dispose of existing segment
                 if brailleElement.offset != 0.0:
+                    # Correct use of end hyphen depends on knowledge of line length (Ex. 10-5)
+                    # So just be conservative and add it
                     currentSegment.endHyphen = True
+
+                # Start new segment and increment number if this is a midmeasure segment start
                 allSegments.append(currentSegment)
                 currentSegment = BrailleSegment()
-                elementsInCurrentSegment = 0
-
                 if brailleElement.offset != 0.0:
                     currentSegment.beginsMidMeasure = True
 
+                elementsInCurrentSegment = 0
+                if previousAffinityCode is not Affinity._LOWEST:
+                    ordinal += 1
+
                 startANewSegment = False
-                if 'BrailleSegmentDivision' in brailleElement.classes:
-                    continue
+            if 'BrailleSegmentDivision' in brailleElement.classes:
+                if ('BrailleOptionalSegmentDivision' in brailleElement.classes
+                        and elementsInCurrentSegment <= MAX_ELEMENTS_IN_SEGMENT):
+                    # Optional condition not met -- pass
+                    pass
+                # Optional condition met, or required
+                elif elementsInCurrentSegment > 0:
+                    startANewSegment = True
+                # All cases: continue, so we don't add anything to the segment
+                continue
+            elif (
+                'Barline' in brailleElement.classes
+                and elementsInCurrentSegment > MAX_ELEMENTS_IN_SEGMENT
+                and brailleElement.type in ('double', 'final')
+            ):
+                # see test_drill10_2
+                startANewSegment = True
+                # execute the block below to ensure barline is added to current segment
 
             if brailleElement.affinityCode < previousAffinityCode:
-                offsetFactor += 1
+                ordinal += 1
 
             affinityCode = brailleElement.affinityCode
             if affinityCode == Affinity.SPLIT1_NOTEGROUP:
@@ -1898,7 +1930,7 @@ def getRawSegments(music21Part, setHand=None):
                 affinityCode = Affinity.NOTEGROUP
 
             segmentKey = SegmentKey(music21Measure.number,
-                                    offsetFactor,
+                                    ordinal,
                                     affinityCode,
                                     setHand
                                     )
@@ -1908,6 +1940,7 @@ def getRawSegments(music21Part, setHand=None):
             brailleElementGrouping.append(brailleElement)
             elementsInCurrentSegment += 1
 
+            # NOT variable affinityCode!
             previousAffinityCode = brailleElement.affinityCode
     allSegments.append(currentSegment)
     return allSegments


Test patch:
diff --git a/music21/braille/test.py b/music21/braille/test.py
index bdafe7dd5..1231add16 100644
--- a/music21/braille/test.py
+++ b/music21/braille/test.py
@@ -1985,7 +1985,127 @@ class Test(unittest.TestCase):
         ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠩⠩⠼⠙⠲⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
         ⠼⠁⠀⠐⠱⠻⠪⠻⠀⠑⠋⠛⠓⠎⠣⠅⠄⠀⠣⠣⠣⠀⠐⠫⠳⠺⠳⠀⠋⠛⠓⠊⠞⠣⠅⠄⠀⠼⠙⠩
         ⠀⠀⠐⠫⠳⠺⠳⠀⠋⠛⠓⠊⠞⠣⠅⠄⠀⠣⠀⠐⠻⠪⠹⠪⠀⠛⠓⠊⠚⠝⠣⠅⠄⠀⠼⠋⠣
-        ⠀⠀⠐⠳⠺⠱⠺⠀⠓⠊⠚⠙⠕⠣⠅⠄⠀⠨⠱⠺⠳⠺⠀⠑⠙⠚⠊⠗⠣⠅⠄
+        ⠀⠀⠐⠳⠺⠱⠺⠀⠓⠊⠚⠙⠕⠣⠅⠄
+        ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+        ⠼⠁⠁⠀⠨⠱⠺⠳⠺⠀⠑⠙⠚⠊⠗⠣⠅⠄
+        '''
+        self.e = '''
+        ---begin segment---
+        <music21.braille.segment BrailleSegment>
+        Measure 1, Signature Grouping 1:
+        Key Signature 2 sharp(s) ⠩⠩
+        Time Signature 4/4 ⠼⠙⠲
+        ===
+        Measure 1, Note Grouping 1:
+        <music21.clef.TrebleClef>
+        Octave 4 ⠐
+        D quarter ⠱
+        F quarter ⠻
+        A quarter ⠪
+        F quarter ⠻
+        ===
+        Measure 2, Note Grouping 1:
+        D eighth ⠑
+        E eighth ⠋
+        F eighth ⠛
+        G eighth ⠓
+        A half ⠎
+        Barline double ⠣⠅⠄
+        ===
+        Measure 3, Signature Grouping 1:
+        Key Signature 3 flat(s) ⠣⠣⠣
+        ===
+        Measure 3, Note Grouping 1:
+        Octave 4 ⠐
+        E quarter ⠫
+        G quarter ⠳
+        B quarter ⠺
+        G quarter ⠳
+        ===
+        Measure 4, Note Grouping 1:
+        E eighth ⠋
+        F eighth ⠛
+        G eighth ⠓
+        A eighth ⠊
+        B half ⠞
+        Barline double ⠣⠅⠄
+        ===
+        Measure 5, Signature Grouping 1:
+        Key Signature 4 sharp(s) ⠼⠙⠩
+        ===
+        Measure 5, Note Grouping 1:
+        Octave 4 ⠐
+        E quarter ⠫
+        G quarter ⠳
+        B quarter ⠺
+        G quarter ⠳
+        ===
+        Measure 6, Note Grouping 1:
+        E eighth ⠋
+        F eighth ⠛
+        G eighth ⠓
+        A eighth ⠊
+        B half ⠞
+        Barline double ⠣⠅⠄
+        ===
+        Measure 7, Signature Grouping 1:
+        Key Signature 1 flat(s) ⠣
+        ===
+        Measure 7, Note Grouping 1:
+        Octave 4 ⠐
+        F quarter ⠻
+        A quarter ⠪
+        C quarter ⠹
+        A quarter ⠪
+        ===
+        Measure 8, Note Grouping 1:
+        F eighth ⠛
+        G eighth ⠓
+        A eighth ⠊
+        B eighth ⠚
+        C half ⠝
+        Barline double ⠣⠅⠄
+        ===
+        Measure 9, Signature Grouping 1:
+        Key Signature 6 flat(s) ⠼⠋⠣
+        ===
+        Measure 9, Note Grouping 1:
+        Octave 4 ⠐
+        G quarter ⠳
+        B quarter ⠺
+        D quarter ⠱
+        B quarter ⠺
+        ===
+        Measure 10, Note Grouping 1:
+        G eighth ⠓
+        A eighth ⠊
+        B eighth ⠚
+        C eighth ⠙
+        D half ⠕
+        Barline double ⠣⠅⠄
+        ===
+        ---end segment---
+        ---begin segment---
+        <music21.braille.segment BrailleSegment>
+        Measure 11, Signature Grouping 1:
+        <music21.key.KeySignature of no sharps or flats>
+        ===
+        Measure 11, Note Grouping 1:
+        Octave 5 ⠨
+        D quarter ⠱
+        B quarter ⠺
+        G quarter ⠳
+        B quarter ⠺
+        ===
+        Measure 12, Note Grouping 1:
+        D eighth ⠑
+        C eighth ⠙
+        B eighth ⠚
+        A eighth ⠊
+        G half ⠗
+        Barline double ⠣⠅⠄
+        ===
+        ---end segment---
         '''
     # test_drill10_3 -- requires alternate time signature symbols
 
@@ -2168,6 +2288,137 @@ Barline final ⠣⠅
         ⠼⠓⠄⠀⠐⠳⠀⠳⠄⠛⠻⠻⠀⠎⠳⠺⠀⠺⠡⠪⠪⠹⠀⠞⠄⠺⠀⠨⠫⠐⠺⠪⠳⠀⠗⠻⠨⠹
         ⠀⠀⠨⠹⠧⠐⠻⠧⠀⠎⠄⠱⠀⠏⠄⠣⠅
         '''
+        self.e = '''
+        ---begin segment---
+        <music21.braille.segment BrailleSegment>
+        Measure 0, Signature Grouping 1:
+        Key Signature 3 flat(s) ⠣⠣⠣
+        Time Signature 4/4 ⠼⠙⠲
+        ===
+        Measure 0, Note Grouping 1:
+        <music21.clef.TrebleClef>
+        Octave 4 ⠐
+        B quarter ⠺
+        ===
+        Measure 1, Note Grouping 1:
+        G quarter ⠳
+        E quarter ⠫
+        D quarter ⠱
+        E quarter ⠫
+        ===
+        Measure 2, Note Grouping 1:
+        G half ⠗
+        F quarter ⠻
+        E quarter ⠫
+        ===
+        Measure 3, Note Grouping 1:
+        A quarter ⠪
+        G quarter ⠳
+        Octave 5 ⠨
+        C quarter ⠹
+        Dot ⠄
+        C eighth ⠙
+        ===
+        Measure 4, Note Grouping 1:
+        B half ⠞
+        Dot ⠄
+        B quarter ⠺
+        ===
+        Measure 5, Note Grouping 1:
+        Octave 5 ⠨
+        E quarter ⠫
+        Octave 4 ⠐
+        B quarter ⠺
+        A quarter ⠪
+        Dot ⠄
+        G eighth ⠓
+        ===
+        Measure 6, Note Grouping 1:
+        G half ⠗
+        F quarter ⠻
+        Octave 5 ⠨
+        C quarter ⠹
+        ===
+        Measure 7, Note Grouping 1:
+        Octave 5 ⠨
+        C quarter ⠹
+        Octave 4 ⠐
+        F quarter ⠻
+        A quarter ⠪
+        Dot ⠄
+        D eighth ⠑
+        ===
+        Measure 8, Note Grouping 1:
+        E half ⠏
+        Dot ⠄
+        music hyphen ⠐
+        ===
+        ---end segment---
+        ---begin segment---
+        <music21.braille.segment BrailleSegment>
+        Measure 8, Note Grouping 2:
+        Octave 4 ⠐
+        G quarter ⠳
+        ===
+        Measure 9, Note Grouping 1:
+        G quarter ⠳
+        Dot ⠄
+        F eighth ⠛
+        F quarter ⠻
+        F quarter ⠻
+        ===
+        Measure 10, Note Grouping 1:
+        A half ⠎
+        G quarter ⠳
+        B quarter ⠺
+        ===
+        Measure 11, Note Grouping 1:
+        B quarter ⠺
+        Accidental natural ⠡
+        A quarter ⠪
+        A quarter ⠪
+        C quarter ⠹
+        ===
+        Measure 12, Note Grouping 1:
+        B half ⠞
+        Dot ⠄
+        B quarter ⠺
+        ===
+        Measure 13, Note Grouping 1:
+        Octave 5 ⠨
+        E quarter ⠫
+        Octave 4 ⠐
+        B quarter ⠺
+        A quarter ⠪
+        G quarter ⠳
+        ===
+        Measure 14, Note Grouping 1:
+        G half ⠗
+        F quarter ⠻
+        Octave 5 ⠨
+        C quarter ⠹
+        ===
+        Measure 15, Note Grouping 1:
+        Octave 5 ⠨
+        C quarter ⠹
+        Rest quarter ⠧
+        Octave 4 ⠐
+        F quarter ⠻
+        Rest quarter ⠧
+        ===
+        Measure 16, Note Grouping 1:
+        A half ⠎
+        Dot ⠄
+        D quarter ⠱
+        ===
+        Measure 17, Note Grouping 1:
+        E half ⠏
+        Dot ⠄
+        Barline final ⠣⠅
+        ===
+        ---end segment---
+        '''
+
 # ------------------------------------------------------------------------------
 # Chapter 12: Slurs (Phrasing)