Repository: cuthbertLab/music21
Pull request #1409 (2022-09-06 19:05:29)

Fix for issue #1408 (ever-increasing voice numbers in multistaff scor…
…es with multi-voice staves).


Patch:
diff --git a/music21/converter/__init__.py b/music21/converter/__init__.py
index 4a227e389..b548754df 100644
--- a/music21/converter/__init__.py
+++ b/music21/converter/__init__.py
@@ -750,7 +750,7 @@ class Converter:
 
         if forceSource is True or not fp.exists():
             environLocal.printDebug([f'downloading to: {fp}'])
-            r = requests.get(url, allow_redirects=True, timeout=20)
+            r = requests.get(url, allow_redirects=True)
             if r.status_code != 200:
                 raise ConverterException(
                     f'Could not download {url}, error: {r.status_code} {responses[r.status_code]}')
diff --git a/music21/key.py b/music21/key.py
index 854a6ac98..0c8d08589 100644
--- a/music21/key.py
+++ b/music21/key.py
@@ -68,23 +68,13 @@ def convertKeyStringToMusic21KeyString(textString):
     'b#'
     >>> key.convertKeyStringToMusic21KeyString('c')
     'c'
-    >>> key.convertKeyStringToMusic21KeyString('Bbb')
-    'B--'
-    >>> key.convertKeyStringToMusic21KeyString('bbb')
-    'b--'
-    >>> key.convertKeyStringToMusic21KeyString('Ebb')
-    'E--'
     '''
-    if (not textString.endswith('b')) or textString == 'b':
-        pass
-    elif textString == 'bb':
+    if textString == 'bb':
         textString = 'b-'
     elif textString == 'Bb':
         textString = 'B-'
-    elif len(textString) == 2 and textString[-1] == 'b':
-        textString = textString[0] + '-'
-    elif set(textString[1:]) == {'b'}:
-        textString = textString[0] + '-' * (len(textString) - 1)
+    elif textString.endswith('b') and not textString.startswith('b'):
+        textString = textString.rstrip('b') + '-'
     return textString
 
 
diff --git a/music21/mei/test/__init__.py b/music21/mei/test/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/music21/musicxml/m21ToXml.py b/music21/musicxml/m21ToXml.py
index 9bb6489d1..cb6c34938 100644
--- a/music21/musicxml/m21ToXml.py
+++ b/music21/musicxml/m21ToXml.py
@@ -1765,8 +1765,8 @@ class ScoreExporter(XMLExporterBase, PartStaffExporterMixin):
             # renumber the voices in this StaffGroup
             staffGroupScore = stream.Score(partExp.staffGroup.getSpannedElements())
             measuresStream = staffGroupScore.recurse().getElementsByClass(stream.Measure).stream()
-            nextVoiceId: int = 1
             for measureStack in OffsetIterator(measuresStream):
+                nextVoiceId: int = 1
                 for m in measureStack:
                     for v in m[stream.Voice]:
                         if not isinstance(v.id, int):
@@ -6244,26 +6244,22 @@ class MeasureExporter(XMLExporterBase):
         # TODO: coda
         # TODO: fermata
 
-        if self.rbSpanners:
-            if position == 'left' and self.rbSpanners[0].isFirst(self.stream):
+        if self.rbSpanners:  # and self.rbSpanners[0].isFirst(m)???
+            mxEnding = Element('ending')
+            if position == 'left':
                 endingType = 'start'
-            elif position == 'right' and self.rbSpanners[0].isLast(self.stream):
-                endingType = 'stop'
             else:
-                endingType = ''
-
-            if endingType:
-                mxEnding = Element('ending')
-                numberList = self.rbSpanners[0].getNumberList()
-                numberStr = str(numberList[0])
-                # 0 is not a valid "ending-number"
-                if numberStr == '0':
-                    numberStr = ''
-                for num in numberList[1:]:
-                    numberStr += ',' + str(num)  # comma-separated ending numbers
-                mxEnding.set('number', numberStr)
-                mxEnding.set('type', endingType)
-                mxBarline.append(mxEnding)  # make sure it is after fermata but before repeat.
+                endingType = 'stop'
+            numberList = self.rbSpanners[0].getNumberList()
+            numberStr = str(numberList[0])
+            # 0 is not a valid "ending-number"
+            if numberStr == '0':
+                numberStr = ''
+            for num in numberList[1:]:
+                numberStr += ',' + str(num)  # comma-separated ending numbers
+            mxEnding.set('number', numberStr)
+            mxEnding.set('type', endingType)
+            mxBarline.append(mxEnding)  # make sure it is after fermata but before repeat.
 
         if mxRepeat is not None:
             mxBarline.append(mxRepeat)
diff --git a/music21/romanText/translate.py b/music21/romanText/translate.py
index 9a1d3a9ca..53b04059d 100644
--- a/music21/romanText/translate.py
+++ b/music21/romanText/translate.py
@@ -308,8 +308,6 @@ def _getKeyAndPrefix(rtKeyOrString):
     (<music21.key.Key of b- minor>, 'b-: ')
     >>> romanText.translate._getKeyAndPrefix('b#')
     (<music21.key.Key of b# minor>, 'b#: ')
-    >>> romanText.translate._getKeyAndPrefix('Bbb')
-    (<music21.key.Key of B-- major>, 'B--: ')
     '''
     if isinstance(rtKeyOrString, str):
         rtKeyOrString = key.convertKeyStringToMusic21KeyString(rtKeyOrString)
diff --git a/setup.py b/setup.py
index 7339b2602..09164ca0c 100644
--- a/setup.py
+++ b/setup.py
@@ -75,7 +75,7 @@ if __name__ == '__main__':
         url='https://github.com/cuthbertLab/music21',
         classifiers=classifiers,
         download_url=f'{download_base}v{m21version}/music21-{m21version}.tar.gz',
-        packages=setuptools.find_namespace_packages(),
+        packages=setuptools.find_packages(exclude=['ez_setup']),
         install_requires=INSTALL_REQUIRES,
         include_package_data=True,
         zip_safe=False,


Test patch:
diff --git a/music21/musicxml/testPrimitive.py b/music21/musicxml/testPrimitive.py
index 37938bb65..34a6ba77c 100644
--- a/music21/musicxml/testPrimitive.py
+++ b/music21/musicxml/testPrimitive.py
@@ -18627,334 +18627,6 @@ pianoStaffPolymeterWithClefOctaveChange = (
 </score-partwise>
 ''')
 
-multiMeasureEnding = (
-    '''<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE score-partwise  PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN"
- "http://www.musicxml.org/dtds/partwise.dtd">
-<score-partwise version="3.1">
-  <work>
-    <work-title>Trio (snippet)</work-title>
-  </work>
-  <movement-number>1</movement-number>
-  <identification>
-    <creator type="composer">Chopin, Fryderyk</creator>
-    <encoding>
-      <encoding-date>2022-08-27</encoding-date>
-      <software>music21 v.8.0.0a12</software>
-    </encoding>
-  </identification>
-  <defaults>
-    <scaling>
-      <millimeters>7</millimeters>
-      <tenths>40</tenths>
-    </scaling>
-  </defaults>
-  <part-list>
-    <score-part id="P2c54d8474d928bcfb818261a5e9fa301">
-      <part-name>ALLEGRO | CON | FUOCO.</part-name>
-    </score-part>
-  </part-list>
-  <!--=========================== Part 1 ===========================-->
-  <part id="P2c54d8474d928bcfb818261a5e9fa301">
-    <!--========================= Measure 1 =========================-->
-    <measure number="1">
-      <note>
-        <pitch>
-          <step>G</step>
-          <octave>4</octave>
-        </pitch>
-        <duration>10080</duration>
-        <voice>1</voice>
-        <type>quarter</type>
-      </note>
-      <note>
-        <rest />
-        <duration>10080</duration>
-        <voice>1</voice>
-        <type>quarter</type>
-      </note>
-      <note>
-        <rest />
-        <duration>20160</duration>
-        <voice>1</voice>
-        <type>half</type>
-      </note>
-      <barline location="right">
-        <bar-style>regular</bar-style>
-      </barline>
-    </measure>
-    <!--========================= Measure 2 =========================-->
-    <measure number="2">
-      <barline location="left">
-        <ending number="1" type="start" />
-      </barline>
-      <note>
-        <rest />
-        <duration>20160</duration>
-        <voice>1</voice>
-        <type>half</type>
-      </note>
-      <direction>
-        <direction-type>
-          <dynamics>
-            <p />
-          </dynamics>
-        </direction-type>
-        <sound dynamics="44" />
-      </direction>
-      <direction placement="below">
-        <direction-type>
-          <wedge number="3" spread="0" type="crescendo" />
-        </direction-type>
-      </direction>
-      <note>
-        <pitch>
-          <step>F</step>
-          <alter>1</alter>
-          <octave>4</octave>
-        </pitch>
-        <duration>20160</duration>
-        <tie type="start" />
-        <voice>1</voice>
-        <type>half</type>
-        <accidental>sharp</accidental>
-        <notations>
-          <tied type="start" />
-        </notations>
-      </note>
-      <direction placement="below">
-        <direction-type>
-          <wedge number="3" spread="15" type="stop" />
-        </direction-type>
-      </direction>
-      <barline location="right">
-        <bar-style>regular</bar-style>
-      </barline>
-    </measure>
-    <!--========================= Measure 3 =========================-->
-    <measure number="3">
-      <direction placement="below">
-        <direction-type>
-          <wedge number="4" spread="15" type="diminuendo" />
-        </direction-type>
-      </direction>
-      <note>
-        <pitch>
-          <step>F</step>
-          <alter>1</alter>
-          <octave>4</octave>
-        </pitch>
-        <duration>40320</duration>
-        <tie type="stop" />
-        <tie type="start" />
-        <voice>1</voice>
-        <type>whole</type>
-        <accidental>sharp</accidental>
-        <notations>
-          <tied type="stop" />
-          <tied type="start" />
-        </notations>
-      </note>
-      <direction placement="below">
-        <direction-type>
-          <wedge number="4" spread="0" type="stop" />
-        </direction-type>
-      </direction>
-      <barline location="right">
-        <bar-style>regular</bar-style>
-      </barline>
-    </measure>
-    <!--========================= Measure 4 =========================-->
-    <measure number="4">
-      <note>
-        <pitch>
-          <step>F</step>
-          <alter>1</alter>
-          <octave>4</octave>
-        </pitch>
-        <duration>40320</duration>
-        <tie type="stop" />
-        <voice>1</voice>
-        <type>whole</type>
-        <accidental>sharp</accidental>
-        <notations>
-          <tied type="stop" />
-        </notations>
-      </note>
-      <barline location="right">
-        <ending number="1" type="stop" />
-        <repeat direction="backward" />
-      </barline>
-    </measure>
-    <!--========================= Measure 5 =========================-->
-    <measure number="5">
-      <barline location="left">
-        <ending number="2" type="start" />
-      </barline>
-      <note>
-        <rest />
-        <duration>20160</duration>
-        <voice>1</voice>
-        <type>half</type>
-      </note>
-      <direction>
-        <direction-type>
-          <dynamics>
-            <p />
-          </dynamics>
-        </direction-type>
-        <sound dynamics="44" />
-      </direction>
-      <direction placement="below">
-        <direction-type>
-          <wedge number="5" spread="15" type="diminuendo" />
-        </direction-type>
-      </direction>
-      <note>
-        <pitch>
-          <step>A</step>
-          <octave>4</octave>
-        </pitch>
-        <duration>20160</duration>
-        <tie type="start" />
-        <voice>1</voice>
-        <type>half</type>
-        <notations>
-          <tied type="start" />
-        </notations>
-      </note>
-      <barline location="right">
-        <bar-style>regular</bar-style>
-      </barline>
-    </measure>
-    <!--========================= Measure 6 =========================-->
-    <measure number="6">
-      <note>
-        <pitch>
-          <step>A</step>
-          <octave>4</octave>
-        </pitch>
-        <duration>20160</duration>
-        <tie type="stop" />
-        <voice>1</voice>
-        <type>half</type>
-        <notations>
-          <tied type="stop" />
-        </notations>
-      </note>
-      <direction placement="below">
-        <direction-type>
-          <wedge number="5" spread="0" type="stop" />
-        </direction-type>
-      </direction>
-      <note>
-        <pitch>
-          <step>G</step>
-          <alter>1</alter>
-          <octave>4</octave>
-        </pitch>
-        <duration>20160</duration>
-        <voice>1</voice>
-        <type>half</type>
-        <accidental>sharp</accidental>
-      </note>
-      <barline location="right">
-        <bar-style>regular</bar-style>
-      </barline>
-    </measure>
-    <!--========================= Measure 7 =========================-->
-    <measure number="7">
-      <note>
-        <pitch>
-          <step>G</step>
-          <alter>0</alter>
-          <octave>4</octave>
-        </pitch>
-        <duration>40320</duration>
-        <voice>1</voice>
-        <type>whole</type>
-        <accidental>natural</accidental>
-      </note>
-      <barline location="right">
-        <bar-style>light-heavy</bar-style>
-        <ending number="2" type="stop" />
-      </barline>
-    </measure>
-    <!--========================= Measure 8 =========================-->
-    <measure number="8">
-      <direction>
-        <direction-type>
-          <words enclosure="none" font-style="italic" font-weight="bold">Risoluto.</words>
-        </direction-type>
-      </direction>
-      <direction>
-        <direction-type>
-          <dynamics>
-            <f />
-          </dynamics>
-        </direction-type>
-        <sound dynamics="88" />
-      </direction>
-      <note>
-        <pitch>
-          <step>F</step>
-          <octave>4</octave>
-        </pitch>
-        <duration>20160</duration>
-        <voice>1</voice>
-        <type>half</type>
-      </note>
-      <note>
-        <pitch>
-          <step>G</step>
-          <octave>4</octave>
-        </pitch>
-        <duration>10080</duration>
-        <voice>1</voice>
-        <type>quarter</type>
-        <notations>
-          <articulations>
-            <staccato />
-          </articulations>
-        </notations>
-      </note>
-      <note>
-        <pitch>
-          <step>A</step>
-          <octave>4</octave>
-        </pitch>
-        <duration>5040</duration>
-        <voice>1</voice>
-        <type>eighth</type>
-        <notations>
-          <articulations>
-            <staccato />
-          </articulations>
-        </notations>
-      </note>
-      <note>
-        <rest />
-        <duration>2520</duration>
-        <voice>1</voice>
-        <type>16th</type>
-      </note>
-      <note>
-        <pitch>
-          <step>D</step>
-          <octave>5</octave>
-        </pitch>
-        <duration>2520</duration>
-        <voice>1</voice>
-        <type>16th</type>
-      </note>
-      <barline location="right">
-        <bar-style>regular</bar-style>
-      </barline>
-    </measure>
-  </part>
-</score-partwise>
-''')
-
 
 ALL = [
     articulations01, pitches01a, directions31a, lyricsMelisma61d, notations32a,  # 0
@@ -18972,8 +18644,7 @@ ALL = [
     colors01, triplets01, textBoxes01, octaveShifts33d,  # 40
     unicodeStrNoNonAscii, unicodeStrWithNonAscii,  # 44
     tremoloTest, hiddenRests, multiDigitEnding, tupletsImplied, pianoStaffPolymeter,  # 46
-    arpeggio32d, multiStaffArpeggios, multiMeasureEnding,   # 51
-    pianoStaffPolymeterWithClefOctaveChange,  # 54
+    arpeggio32d, multiStaffArpeggios  # 51
 ]
 
 
diff --git a/music21/musicxml/test_m21ToXml.py b/music21/musicxml/test_m21ToXml.py
index fe1277bf3..318b66189 100644
--- a/music21/musicxml/test_m21ToXml.py
+++ b/music21/musicxml/test_m21ToXml.py
@@ -180,7 +180,7 @@ class Test(unittest.TestCase):
         staffGroup = layout.StaffGroup([ps1, ps2, ps3])
         s.insert(0, staffGroup)
 
-        tree = self.getET(s)
+        tree = self.getET(s, makeNotation=False)
         # helpers.dump(tree)
         mxNotes = tree.findall('part/measure/note')
         for mxNote in mxNotes:
@@ -413,72 +413,6 @@ class Test(unittest.TestCase):
         endings = x.findall('.//ending')
         self.assertEqual([e.get('number') for e in endings], ['', '', '3', '3'])
 
-    def testMultiMeasureEndingsWrite(self):
-        # Relevant barlines:
-        # Measure 1, left barline: no ending
-        # Measure 1, right barline: no ending
-        # Measure 2, left barline: <ending number="1" type="start"/>
-        # Measure 2, right barline: no ending
-        # Measure 3, left barline: no ending
-        # Measure 3, right barline: no ending
-        # Measure 4, left barline: no ending
-        # Measure 4, right barline: <ending number="1" type="stop"/>
-        # Measure 5, left barline: <ending number="2" type="start"/>
-        # Measure 5, right barline: no ending
-        # Measure 6, left barline: no ending
-        # Measure 6, right barline: no ending
-        # Measure 7, left barline: no ending
-        # Measure 7, right barline: <ending number="2" type="stop"/>
-        # Measure 8, left barline: no ending
-        # Measure 8, right barline: no ending
-        s = converter.parse(testPrimitive.multiMeasureEnding)
-        x = self.getET(s)
-        endings = x.findall('.//ending')
-        self.assertEqual([e.get('number') for e in endings], ['1', '1', '2', '2'])
-
-        expectedEndings = {
-            # key = measure number, value = list(leftEndingType, rightEndingType)
-            '1': [None, None],     # measure before the endings
-            '2': ['start', None],  # first measure of ending 1
-            '3': [None, None],     # second measure of ending 1
-            '4': [None, 'stop'],   # last measure of ending 1
-            '5': ['start', None],  # first measure of ending 2
-            '6': [None, None],     # second measure of ending 2
-            '7': [None, 'stop'],   # last measure of ending 2
-            '8': [None, None]      # measure after the endings
-        }
-        measures = x.findall('.//measure')
-        self.assertEqual(len(measures), 8)
-        for measure in measures:
-            measNumber = measure.get('number')
-            with self.subTest(measureNumber=measNumber):
-                expectLeftBarline = bool(expectedEndings[measNumber][0] is not None)
-                expectRightBarline = bool(expectedEndings[measNumber][1] is not None)
-
-                gotLeftBarline = False
-                gotRightBarline = False
-                barlines = measure.findall('.//barline')
-                for i, barline in enumerate(barlines):
-                    if barline.get('location') == 'left':
-                        gotLeftBarline = True
-                        leftEndingType = None
-                        leftEnding = barline.find('ending')
-                        if leftEnding is not None:
-                            leftEndingType = leftEnding.get('type')
-                        self.assertEqual(leftEndingType, expectedEndings[measNumber][0])
-                    elif barline.get('location') == 'right':
-                        gotRightBarline = True
-                        rightEndingType = None
-                        rightEnding = barline.find('ending')
-                        if rightEnding is not None:
-                            rightEndingType = rightEnding.get('type')
-                        self.assertEqual(rightEndingType, expectedEndings[measNumber][1])
-
-                if expectLeftBarline:
-                    self.assertTrue(gotLeftBarline)
-                if expectRightBarline:
-                    self.assertTrue(gotRightBarline)
-
     def testTextExpressionOffset(self):
         '''Transfer element offset after calling getTextExpression().'''
         # https://github.com/cuthbertLab/music21/issues/624