Skip to content

Commit

Permalink
Merge pull request #1361 from cuthbertLab/make-rests-before-notation
Browse files Browse the repository at this point in the history
MusicXML export: Make rests before making notation
  • Loading branch information
mscuthbert authored Aug 8, 2022
2 parents 1565894 + 127b176 commit 1a631f8
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 28 deletions.
54 changes: 26 additions & 28 deletions music21/musicxml/m21ToXml.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,9 +466,29 @@ def fromGeneralObject(self, obj):
{3.0} <music21.bar.Barline type=final>
>>> s[note.NotRest].first().duration
<music21.duration.Duration 3.0>
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
>>> '<note print-object="no" print-spacing="yes">' in outStr
True
'''
classes = obj.classes
outObj = None

if isinstance(obj, stream.Stream) and self.makeNotation:
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)
Expand Down Expand Up @@ -1676,16 +1696,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.
Hide rests created at this late stage.
>>> 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
>>> '<note print-object="no" print-spacing="yes">' in outStr
True
'''
if not self.partExporterList:
self._populatePartExporterList()
Expand Down Expand Up @@ -2641,16 +2651,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):
Expand Down Expand Up @@ -2837,7 +2837,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.
'''
Expand Down Expand Up @@ -2867,20 +2867,18 @@ 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:
try:
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
Expand Down
6 changes: 6 additions & 0 deletions music21/musicxml/test_m21ToXml.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions music21/stream/makeNotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down

0 comments on commit 1a631f8

Please sign in to comment.