From 7988711776ffbfa3a8372a83b61f4681795c5721 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 6 Aug 2022 18:57:05 -0400 Subject: [PATCH 1/6] MusicXML export: Make rests before making notation --- music21/musicxml/m21ToXml.py | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/music21/musicxml/m21ToXml.py b/music21/musicxml/m21ToXml.py index 7518871e77..4fce409823 100644 --- a/music21/musicxml/m21ToXml.py +++ b/music21/musicxml/m21ToXml.py @@ -541,6 +541,17 @@ def fromVoice(self, v): return self.fromMeasure(m) def fromStream(self, st): + ''' + Changed in v8 -- fills gaps with rests before calling makeNotation + to avoid duplicating effort with :meth:`PartExporter.fixupNotationMeasured`. + ''' + st.makeRests(refStreamOrTimeRange=[0.0, st.highestTime], + fillGaps=True, + inPlace=True, + hideRests=True, # just to fill up MusicXML display + timeRangeFromBarDuration=True, + ) + if st.isFlat: st2 = stream.Part() st2.mergeAttributes(st) @@ -2641,16 +2652,6 @@ def parse(self): # Suppose that everything below this is a measure if self.makeNotation: - # hide any rests created at this late stage, because we are - # merely trying to fill up MusicXML display, not impose things on users - self.stream.makeRests(refStreamOrTimeRange=self.refStreamOrTimeRange, - inPlace=True, - hideRests=True, - timeRangeFromBarDuration=True, - ) - - # Split complex durations in place (fast if none found) - # Do this after makeRests since makeRests might create complex durations self.stream = self.stream.splitAtDurations(recurse=True)[0] if self.stream.getElementsByClass(stream.Measure): @@ -2837,7 +2838,7 @@ def fixupNotationMeasured(self): them into the first measure if necessary. Checks if makeAccidentals is run, and haveBeamsBeenMade is done, and - remake tuplets on the assumption that makeRests() may necessitate changes. + tuplets have been made. Changed in v7 -- no longer accepts `measureStream` argument. ''' @@ -2867,7 +2868,7 @@ def fixupNotationMeasured(self): if outerTimeSignatures: first_measure.timeSignature = outerTimeSignatures.first() - # see if accidentals/beams should be processed + # see if accidentals/beams/tuplets should be processed if not part.streamStatus.haveAccidentalsBeenMade(): part.makeAccidentals(inPlace=True) if not part.streamStatus.beams: @@ -2875,12 +2876,10 @@ def fixupNotationMeasured(self): part.makeBeams(inPlace=True) except exceptions21.StreamException as se: # no measures or no time sig? warnings.warn(MusicXMLWarning, str(se)) - # tuplets should be processed anyway (affected by earlier makeRests) - # technically, beams could be affected also, but we don't want to destroy - # existing beam information (e.g. single-syllable vocal flags) - for m in measures: - for m_or_v in [m, *m.voices]: - stream.makeNotation.makeTupletBrackets(m_or_v, inPlace=True) + if not part.streamStatus.tuplets: + for m in measures: + for m_or_v in [m, *m.voices]: + stream.makeNotation.makeTupletBrackets(m_or_v, inPlace=True) if not self.spannerBundle: self.spannerBundle = part.spannerBundle From 0f7b0d3f3120efb101ddc6932416976c8f6660e0 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 6 Aug 2022 19:03:47 -0400 Subject: [PATCH 2/6] makeRests() now invalidates tuplet status --- music21/stream/makeNotation.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/music21/stream/makeNotation.py b/music21/stream/makeNotation.py index dd897f73cd..2e526d24cf 100644 --- a/music21/stream/makeNotation.py +++ b/music21/stream/makeNotation.py @@ -840,6 +840,9 @@ def makeRests( else: returnObj = s + # Invalidate tuplet status + returnObj.streamStatus.tuplets = None + if returnObj.iter().parts: for inner_part in returnObj.iter().parts: inner_part.makeRests( From 3748d7b810a01ffa148bb907343e918588a36e0d Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 6 Aug 2022 19:16:30 -0400 Subject: [PATCH 3/6] remove moot comment --- music21/musicxml/m21ToXml.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/music21/musicxml/m21ToXml.py b/music21/musicxml/m21ToXml.py index 4fce409823..6262656853 100644 --- a/music21/musicxml/m21ToXml.py +++ b/music21/musicxml/m21ToXml.py @@ -1688,8 +1688,6 @@ def parsePartlikeScore(self): Appends the PartExporter to `self.partExporterList` and runs .parse() on that part. Appends the PartExporter to self. - Hide rests created at this late stage. - >>> v = stream.Voice(note.Note()) >>> m = stream.Measure([meter.TimeSignature(), v]) >>> GEX = musicxml.m21ToXml.GeneralObjectExporter(m) From 058ed428565fb93d3a67b274c0530a5430e89ebc Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 6 Aug 2022 19:26:55 -0400 Subject: [PATCH 4/6] working on fixing tests --- music21/musicxml/m21ToXml.py | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/music21/musicxml/m21ToXml.py b/music21/musicxml/m21ToXml.py index 6262656853..40c7e75d7b 100644 --- a/music21/musicxml/m21ToXml.py +++ b/music21/musicxml/m21ToXml.py @@ -466,9 +466,21 @@ def fromGeneralObject(self, obj): {3.0} >>> s[note.NotRest].first().duration + + Changed in v8 -- fills gaps with rests before calling makeNotation + to avoid duplicating effort with :meth:`PartExporter.fixupNotationMeasured`. ''' classes = obj.classes outObj = None + + if isinstance(obj, stream.Stream): + obj.makeRests(refStreamOrTimeRange=[0.0, obj.highestTime], + fillGaps=True, + inPlace=True, + hideRests=True, # just to fill up MusicXML display + timeRangeFromBarDuration=True, + ) + for cM, methName in self.classMapping.items(): if cM in classes: meth = getattr(self, methName) @@ -541,17 +553,6 @@ def fromVoice(self, v): return self.fromMeasure(m) def fromStream(self, st): - ''' - Changed in v8 -- fills gaps with rests before calling makeNotation - to avoid duplicating effort with :meth:`PartExporter.fixupNotationMeasured`. - ''' - st.makeRests(refStreamOrTimeRange=[0.0, st.highestTime], - fillGaps=True, - inPlace=True, - hideRests=True, # just to fill up MusicXML display - timeRangeFromBarDuration=True, - ) - if st.isFlat: st2 = stream.Part() st2.mergeAttributes(st) @@ -1687,14 +1688,6 @@ def parsePartlikeScore(self): Creates a `PartExporter` for each part, and runs .parse() on that part. Appends the PartExporter to `self.partExporterList` and runs .parse() on that part. Appends the PartExporter to self. - - >>> v = stream.Voice(note.Note()) - >>> m = stream.Measure([meter.TimeSignature(), v]) - >>> GEX = musicxml.m21ToXml.GeneralObjectExporter(m) - >>> out = GEX.parse() # out is bytes - >>> outStr = out.decode('utf-8') # now is string - >>> '' in outStr - True ''' if not self.partExporterList: self._populatePartExporterList() From 9b4d236b6d7d38061bbcc88d1443aed980e36433 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 7 Aug 2022 11:27:58 -0400 Subject: [PATCH 5/6] Only make rests when makeNotation is True --- music21/musicxml/m21ToXml.py | 2 +- music21/musicxml/test_m21ToXml.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/music21/musicxml/m21ToXml.py b/music21/musicxml/m21ToXml.py index 40c7e75d7b..0eb688c1b2 100644 --- a/music21/musicxml/m21ToXml.py +++ b/music21/musicxml/m21ToXml.py @@ -473,7 +473,7 @@ def fromGeneralObject(self, obj): classes = obj.classes outObj = None - if isinstance(obj, stream.Stream): + if isinstance(obj, stream.Stream) and self.makeNotation: obj.makeRests(refStreamOrTimeRange=[0.0, obj.highestTime], fillGaps=True, inPlace=True, diff --git a/music21/musicxml/test_m21ToXml.py b/music21/musicxml/test_m21ToXml.py index aee5cf2303..364bbd27ad 100644 --- a/music21/musicxml/test_m21ToXml.py +++ b/music21/musicxml/test_m21ToXml.py @@ -419,9 +419,13 @@ def testMeasurePadding(self): s = stream.Score([converter.parse('tinyNotation: 4/4 c4')]) s[stream.Measure].first().paddingLeft = 2.0 s[stream.Measure].first().paddingRight = 1.0 + # workaround until getET() helper starts calling fromGeneralObject + s = GeneralObjectExporter().fromGeneralObject(s) tree = self.getET(s) self.assertEqual(len(tree.findall('.//rest')), 0) s[stream.Measure].first().paddingLeft = 1.0 + # workaround until getET() helper starts calling fromGeneralObject + s = GeneralObjectExporter().fromGeneralObject(s) tree = self.getET(s) self.assertEqual(len(tree.findall('.//rest')), 1) @@ -455,6 +459,7 @@ def testOutOfBoundsExpressionDoesNotCreateForward(self): m.insert(2, tempo.MetronomeMark('slow', 40)) gex = GeneralObjectExporter() + gex.makeNotation = False tree = self.getET(gex.fromGeneralObject(m)) self.assertFalse(tree.findall('.//forward')) self.assertEqual( @@ -594,6 +599,7 @@ def testArpeggioMarkSpannersNonArpeggiate(self): def testExportChordSymbolsWithRealizedDurations(self): gex = GeneralObjectExporter() + gex.makeNotation = False def realizeDurationsAndAssertTags(mm: stream.Measure, forwardTag=False, offsetTag=False): mm = copy.deepcopy(mm) From 127b176f6c62f6d4aa0a9935d1a2934ca88499af Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 7 Aug 2022 11:29:11 -0400 Subject: [PATCH 6/6] Restore doctest showing hidden rests --- music21/musicxml/m21ToXml.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/music21/musicxml/m21ToXml.py b/music21/musicxml/m21ToXml.py index 0eb688c1b2..0af7d5dd71 100644 --- a/music21/musicxml/m21ToXml.py +++ b/music21/musicxml/m21ToXml.py @@ -469,6 +469,14 @@ def fromGeneralObject(self, obj): Changed in v8 -- fills gaps with rests before calling makeNotation to avoid duplicating effort with :meth:`PartExporter.fixupNotationMeasured`. + + >>> v = stream.Voice(note.Note()) + >>> m = stream.Measure([meter.TimeSignature(), v]) + >>> GEX = musicxml.m21ToXml.GeneralObjectExporter(m) + >>> out = GEX.parse() # out is bytes + >>> outStr = out.decode('utf-8') # now is string + >>> '' in outStr + True ''' classes = obj.classes outObj = None