Skip to content

Commit 1a631f8

Browse files
authored
Merge pull request #1361 from cuthbertLab/make-rests-before-notation
MusicXML export: Make rests before making notation
2 parents 1565894 + 127b176 commit 1a631f8

3 files changed

Lines changed: 35 additions & 28 deletions

File tree

music21/musicxml/m21ToXml.py

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -466,9 +466,29 @@ def fromGeneralObject(self, obj):
466466
{3.0} <music21.bar.Barline type=final>
467467
>>> s[note.NotRest].first().duration
468468
<music21.duration.Duration 3.0>
469+
470+
Changed in v8 -- fills gaps with rests before calling makeNotation
471+
to avoid duplicating effort with :meth:`PartExporter.fixupNotationMeasured`.
472+
473+
>>> v = stream.Voice(note.Note())
474+
>>> m = stream.Measure([meter.TimeSignature(), v])
475+
>>> GEX = musicxml.m21ToXml.GeneralObjectExporter(m)
476+
>>> out = GEX.parse() # out is bytes
477+
>>> outStr = out.decode('utf-8') # now is string
478+
>>> '<note print-object="no" print-spacing="yes">' in outStr
479+
True
469480
'''
470481
classes = obj.classes
471482
outObj = None
483+
484+
if isinstance(obj, stream.Stream) and self.makeNotation:
485+
obj.makeRests(refStreamOrTimeRange=[0.0, obj.highestTime],
486+
fillGaps=True,
487+
inPlace=True,
488+
hideRests=True, # just to fill up MusicXML display
489+
timeRangeFromBarDuration=True,
490+
)
491+
472492
for cM, methName in self.classMapping.items():
473493
if cM in classes:
474494
meth = getattr(self, methName)
@@ -1676,16 +1696,6 @@ def parsePartlikeScore(self):
16761696
Creates a `PartExporter` for each part, and runs .parse() on that part.
16771697
Appends the PartExporter to `self.partExporterList`
16781698
and runs .parse() on that part. Appends the PartExporter to self.
1679-
1680-
Hide rests created at this late stage.
1681-
1682-
>>> v = stream.Voice(note.Note())
1683-
>>> m = stream.Measure([meter.TimeSignature(), v])
1684-
>>> GEX = musicxml.m21ToXml.GeneralObjectExporter(m)
1685-
>>> out = GEX.parse() # out is bytes
1686-
>>> outStr = out.decode('utf-8') # now is string
1687-
>>> '<note print-object="no" print-spacing="yes">' in outStr
1688-
True
16891699
'''
16901700
if not self.partExporterList:
16911701
self._populatePartExporterList()
@@ -2641,16 +2651,6 @@ def parse(self):
26412651

26422652
# Suppose that everything below this is a measure
26432653
if self.makeNotation:
2644-
# hide any rests created at this late stage, because we are
2645-
# merely trying to fill up MusicXML display, not impose things on users
2646-
self.stream.makeRests(refStreamOrTimeRange=self.refStreamOrTimeRange,
2647-
inPlace=True,
2648-
hideRests=True,
2649-
timeRangeFromBarDuration=True,
2650-
)
2651-
2652-
# Split complex durations in place (fast if none found)
2653-
# Do this after makeRests since makeRests might create complex durations
26542654
self.stream = self.stream.splitAtDurations(recurse=True)[0]
26552655

26562656
if self.stream.getElementsByClass(stream.Measure):
@@ -2837,7 +2837,7 @@ def fixupNotationMeasured(self):
28372837
them into the first measure if necessary.
28382838
28392839
Checks if makeAccidentals is run, and haveBeamsBeenMade is done, and
2840-
remake tuplets on the assumption that makeRests() may necessitate changes.
2840+
tuplets have been made.
28412841
28422842
Changed in v7 -- no longer accepts `measureStream` argument.
28432843
'''
@@ -2867,20 +2867,18 @@ def fixupNotationMeasured(self):
28672867
if outerTimeSignatures:
28682868
first_measure.timeSignature = outerTimeSignatures.first()
28692869

2870-
# see if accidentals/beams should be processed
2870+
# see if accidentals/beams/tuplets should be processed
28712871
if not part.streamStatus.haveAccidentalsBeenMade():
28722872
part.makeAccidentals(inPlace=True)
28732873
if not part.streamStatus.beams:
28742874
try:
28752875
part.makeBeams(inPlace=True)
28762876
except exceptions21.StreamException as se: # no measures or no time sig?
28772877
warnings.warn(MusicXMLWarning, str(se))
2878-
# tuplets should be processed anyway (affected by earlier makeRests)
2879-
# technically, beams could be affected also, but we don't want to destroy
2880-
# existing beam information (e.g. single-syllable vocal flags)
2881-
for m in measures:
2882-
for m_or_v in [m, *m.voices]:
2883-
stream.makeNotation.makeTupletBrackets(m_or_v, inPlace=True)
2878+
if not part.streamStatus.tuplets:
2879+
for m in measures:
2880+
for m_or_v in [m, *m.voices]:
2881+
stream.makeNotation.makeTupletBrackets(m_or_v, inPlace=True)
28842882

28852883
if not self.spannerBundle:
28862884
self.spannerBundle = part.spannerBundle

music21/musicxml/test_m21ToXml.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,9 +419,13 @@ def testMeasurePadding(self):
419419
s = stream.Score([converter.parse('tinyNotation: 4/4 c4')])
420420
s[stream.Measure].first().paddingLeft = 2.0
421421
s[stream.Measure].first().paddingRight = 1.0
422+
# workaround until getET() helper starts calling fromGeneralObject
423+
s = GeneralObjectExporter().fromGeneralObject(s)
422424
tree = self.getET(s)
423425
self.assertEqual(len(tree.findall('.//rest')), 0)
424426
s[stream.Measure].first().paddingLeft = 1.0
427+
# workaround until getET() helper starts calling fromGeneralObject
428+
s = GeneralObjectExporter().fromGeneralObject(s)
425429
tree = self.getET(s)
426430
self.assertEqual(len(tree.findall('.//rest')), 1)
427431

@@ -455,6 +459,7 @@ def testOutOfBoundsExpressionDoesNotCreateForward(self):
455459
m.insert(2, tempo.MetronomeMark('slow', 40))
456460

457461
gex = GeneralObjectExporter()
462+
gex.makeNotation = False
458463
tree = self.getET(gex.fromGeneralObject(m))
459464
self.assertFalse(tree.findall('.//forward'))
460465
self.assertEqual(
@@ -594,6 +599,7 @@ def testArpeggioMarkSpannersNonArpeggiate(self):
594599

595600
def testExportChordSymbolsWithRealizedDurations(self):
596601
gex = GeneralObjectExporter()
602+
gex.makeNotation = False
597603

598604
def realizeDurationsAndAssertTags(mm: stream.Measure, forwardTag=False, offsetTag=False):
599605
mm = copy.deepcopy(mm)

music21/stream/makeNotation.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,9 @@ def makeRests(
840840
else:
841841
returnObj = s
842842

843+
# Invalidate tuplet status
844+
returnObj.streamStatus.tuplets = None
845+
843846
if returnObj.iter().parts:
844847
for inner_part in returnObj.iter().parts:
845848
inner_part.makeRests(

0 commit comments

Comments
 (0)