Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MusicXML export: Make rests before making notation #1361

Merged
merged 6 commits into from
Aug 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Comment on lines -1682 to -1688
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is moved to fromGeneralObject() doctest

'''
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)
Comment on lines +422 to +423
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We really need to get all the musicxml tests to declare whether or not they depend on notation being made. This was a problem before we added makeNotation=False as an option -- essentially, getET() bypasses part of the normal musicxml export path.

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