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().'''