From cac47845b232ac402917db8500cad63cf587eb9e Mon Sep 17 00:00:00 2001
From: Greg Chapman <75333244+gregchapman-dev@users.noreply.github.com>
Date: Sat, 27 Aug 2022 12:59:31 -0700
Subject: [PATCH 1/3] Fix (and test) writing of multi-measure RepeatBrackets to
MusicXML.
---
music21/musicxml/m21ToXml.py | 32 +--
music21/musicxml/testPrimitive.py | 328 ++++++++++++++++++++++++++++++
music21/musicxml/test_m21ToXml.py | 65 ++++++
3 files changed, 411 insertions(+), 14 deletions(-)
diff --git a/music21/musicxml/m21ToXml.py b/music21/musicxml/m21ToXml.py
index 949d0252ff..9bb6489d11 100644
--- a/music21/musicxml/m21ToXml.py
+++ b/music21/musicxml/m21ToXml.py
@@ -6244,22 +6244,26 @@ def setBarline(self, barline, position):
# TODO: coda
# TODO: fermata
- if self.rbSpanners: # and self.rbSpanners[0].isFirst(m)???
- mxEnding = Element('ending')
- if position == 'left':
+ if self.rbSpanners:
+ if position == 'left' and self.rbSpanners[0].isFirst(self.stream):
endingType = 'start'
- else:
+ elif position == 'right' and self.rbSpanners[0].isLast(self.stream):
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.
+ 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.
if mxRepeat is not None:
mxBarline.append(mxRepeat)
diff --git a/music21/musicxml/testPrimitive.py b/music21/musicxml/testPrimitive.py
index 34a6ba77c5..74e84a6157 100644
--- a/music21/musicxml/testPrimitive.py
+++ b/music21/musicxml/testPrimitive.py
@@ -18627,6 +18627,334 @@
''')
+multiMeasureEnding = (
+ '''
+
+
+
+ Trio (snippet)
+
+ 1
+
+ Chopin, Fryderyk
+
+ 2022-08-27
+ music21 v.8.0.0a12
+
+
+
+
+ 7
+ 40
+
+
+
+
+ ALLEGRO | CON | FUOCO.
+
+
+
+
+
+
+
+
+ G
+ 4
+
+ 10080
+ 1
+ quarter
+
+
+
+ 10080
+ 1
+ quarter
+
+
+
+ 20160
+ 1
+ half
+
+
+ regular
+
+
+
+
+
+
+
+
+
+ 20160
+ 1
+ half
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ F
+ 1
+ 4
+
+ 20160
+
+ 1
+ half
+ sharp
+
+
+
+
+
+
+
+
+
+
+ regular
+
+
+
+
+
+
+
+
+
+
+
+ F
+ 1
+ 4
+
+ 40320
+
+
+ 1
+ whole
+ sharp
+
+
+
+
+
+
+
+
+
+
+
+ regular
+
+
+
+
+
+
+ F
+ 1
+ 4
+
+ 40320
+
+ 1
+ whole
+ sharp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 20160
+ 1
+ half
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A
+ 4
+
+ 20160
+
+ 1
+ half
+
+
+
+
+
+ regular
+
+
+
+
+
+
+ A
+ 4
+
+ 20160
+
+ 1
+ half
+
+
+
+
+
+
+
+
+
+
+
+ G
+ 1
+ 4
+
+ 20160
+ 1
+ half
+ sharp
+
+
+ regular
+
+
+
+
+
+
+ G
+ 0
+ 4
+
+ 40320
+ 1
+ whole
+ natural
+
+
+ light-heavy
+
+
+
+
+
+
+
+ Risoluto.
+
+
+
+
+
+
+
+
+
+
+
+
+ F
+ 4
+
+ 20160
+ 1
+ half
+
+
+
+ G
+ 4
+
+ 10080
+ 1
+ quarter
+
+
+
+
+
+
+
+
+ A
+ 4
+
+ 5040
+ 1
+ eighth
+
+
+
+
+
+
+
+
+ 2520
+ 1
+ 16th
+
+
+
+ D
+ 5
+
+ 2520
+ 1
+ 16th
+
+
+ regular
+
+
+
+
+''')
+
ALL = [
articulations01, pitches01a, directions31a, lyricsMelisma61d, notations32a, # 0
diff --git a/music21/musicxml/test_m21ToXml.py b/music21/musicxml/test_m21ToXml.py
index 860ad01c7b..bbbaac74c1 100644
--- a/music21/musicxml/test_m21ToXml.py
+++ b/music21/musicxml/test_m21ToXml.py
@@ -413,6 +413,71 @@ def testMultiDigitEndingsWrite(self):
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:
+ # 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:
+ # Measure 5, left barline:
+ # 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:
+ # 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')
+ 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
From 21f193e9fcf36083fc82364530458a2364f285e1 Mon Sep 17 00:00:00 2001
From: Greg Chapman <75333244+gregchapman-dev@users.noreply.github.com>
Date: Sat, 27 Aug 2022 13:19:33 -0700
Subject: [PATCH 2/3] Add new tests to ALL.
---
music21/musicxml/testPrimitive.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/music21/musicxml/testPrimitive.py b/music21/musicxml/testPrimitive.py
index 74e84a6157..37938bb65f 100644
--- a/music21/musicxml/testPrimitive.py
+++ b/music21/musicxml/testPrimitive.py
@@ -18972,7 +18972,8 @@
colors01, triplets01, textBoxes01, octaveShifts33d, # 40
unicodeStrNoNonAscii, unicodeStrWithNonAscii, # 44
tremoloTest, hiddenRests, multiDigitEnding, tupletsImplied, pianoStaffPolymeter, # 46
- arpeggio32d, multiStaffArpeggios # 51
+ arpeggio32d, multiStaffArpeggios, multiMeasureEnding, # 51
+ pianoStaffPolymeterWithClefOctaveChange, # 54
]
From 727270c1ae92e142c21482cc7d5774bacc4e0969 Mon Sep 17 00:00:00 2001
From: Greg Chapman <75333244+gregchapman-dev@users.noreply.github.com>
Date: Sun, 28 Aug 2022 14:40:05 -0700
Subject: [PATCH 3/3] Add subTest(measureNumber) to
testMultiMeasureEndingsWrite.
---
music21/musicxml/test_m21ToXml.py | 53 ++++++++++++++++---------------
1 file changed, 27 insertions(+), 26 deletions(-)
diff --git a/music21/musicxml/test_m21ToXml.py b/music21/musicxml/test_m21ToXml.py
index bbbaac74c1..fe1277bf3b 100644
--- a/music21/musicxml/test_m21ToXml.py
+++ b/music21/musicxml/test_m21ToXml.py
@@ -451,32 +451,33 @@ def testMultiMeasureEndingsWrite(self):
self.assertEqual(len(measures), 8)
for measure in measures:
measNumber = measure.get('number')
- 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)
+ 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().'''