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

Fix #1633 #1636

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion music21/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
'''
from __future__ import annotations

__version__ = '9.2.0b1'
__version__ = '9.2.0b2'

def get_version_tuple(vv):
v = vv.split('.')
Expand Down
2 changes: 1 addition & 1 deletion music21/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<class 'music21.base.Music21Object'>

>>> music21.VERSION_STR
'9.2.0b1'
'9.2.0b2'

Alternatively, after doing a complete import, these classes are available
under the module "base":
Expand Down
2 changes: 1 addition & 1 deletion music21/musicxml/test_xmlToM21.py
Original file line number Diff line number Diff line change
Expand Up @@ -1290,7 +1290,7 @@ def testHiddenRestImpliedVoice(self):

self.assertEqual(len(MP.stream.voices), 2)
self.assertEqual(len(MP.stream.voices[0].elements), 1)
self.assertEqual(len(MP.stream.voices[1].elements), 2)
self.assertEqual(len(MP.stream.voices[1].elements), 1)
self.assertEqual(MP.stream.voices[1].id, 'non-integer-value')

def testMultiDigitEnding(self):
Expand Down
94 changes: 25 additions & 69 deletions music21/musicxml/xmlToM21.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from music21 import duration
from music21 import dynamics
from music21.common.enums import OrnamentDelay
from music21.common.numberTools import opFrac
from music21 import editorial
from music21 import environment
from music21 import exceptions21
Expand Down Expand Up @@ -866,6 +867,19 @@ def xmlRootToScore(self, mxScore, inputM21=None):
self.spannerBundle.remove(sp)

s.coreElementsChanged()
# Fill gaps with rests where needed
for m in s[stream.Measure]:
for v in m.voices:
if v: # do not bother with empty voices
# the musicDataMethods use insertCore, thus the voices need to run
# coreElementsChanged
v.coreElementsChanged()
# Fill mid-measure gaps, and find end of measure gaps by ref to measure stream
# https://github.com/cuthbertlab/music21/issues/444
v.makeRests(refStreamOrTimeRange=m,
fillGaps=True,
inPlace=True,
hideRests=True)
Comment on lines +870 to +882
Copy link
Member

Choose a reason for hiding this comment

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

I think this is too presumptive on the part of the parser. We should be interpolating/inventing as few elements as possible that are not in the document.

Copy link
Contributor Author

@TimFelixBeyer TimFelixBeyer Aug 27, 2023

Choose a reason for hiding this comment

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

I agree!
I just put the makeRests there because previously the parser also ran makeRests (this block is basically just copy pasted from before), but I'd be more than happy to remove it.

s.definesExplicitSystemBreaks = self.definesExplicitSystemBreaks
s.definesExplicitPageBreaks = self.definesExplicitPageBreaks
for p in s.parts:
Expand Down Expand Up @@ -1760,32 +1774,8 @@ def parseMeasures(self):
for mxMeasure in self.mxPart.iterfind('measure'):
self.xmlMeasureToMeasure(mxMeasure)

self.removeEndForwardRest()
part.coreElementsChanged()

def removeEndForwardRest(self):
'''
If the last measure ended with a forward tag, as happens
in some pieces that end with incomplete measures,
and voices are not involved,
remove the rest there (for backwards compatibility, esp.
since bwv66.6 uses it)

* New in v7.
'''
if self.lastMeasureParser is None: # pragma: no cover
return # should not happen
lmp = self.lastMeasureParser
self.lastMeasureParser = None # clean memory

if lmp.endedWithForwardTag is None:
return
if lmp.useVoices is True:
return
endedForwardRest = lmp.endedWithForwardTag
if lmp.stream.recurse().notesAndRests.last() is endedForwardRest:
lmp.stream.remove(endedForwardRest, recurse=True)

def separateOutPartStaves(self) -> list[stream.PartStaff]:
'''
Take a `Part` with multiple staves and make them a set of `PartStaff` objects.
Expand Down Expand Up @@ -2233,7 +2223,7 @@ def adjustTimeAttributesFromMeasure(self, m: stream.Measure):
else:
self.lastMeasureWasShort = False

self.lastMeasureOffset += mOffsetShift
self.lastMeasureOffset = opFrac(self.lastMeasureOffset + mOffsetShift)

def applyMultiMeasureRest(self, r: note.Rest):
'''
Expand Down Expand Up @@ -2390,13 +2380,6 @@ def __init__(self,
# what is the offset in the measure of the current note position?
self.offsetMeasureNote: OffsetQL = 0.0

# keep track of the last rest that was added with a forward tag.
# there are many pieces that end with incomplete measures that
# older versions of Finale put a forward tag at the end, but this
# disguises the incomplete last measure. The PartParser will
# pick this up from the last measure.
self.endedWithForwardTag: note.Rest | None = None

@staticmethod
def getStaffNumber(mxObjectOrNumber) -> int:
'''
Expand Down Expand Up @@ -2553,19 +2536,8 @@ def parse(self):
if methName is not None:
meth = getattr(self, methName)
meth(mxObj)

if self.useVoices is True:
for v in self.stream.iter().voices:
if v: # do not bother with empty voices
# the musicDataMethods use insertCore, thus the voices need to run
# coreElementsChanged
v.coreElementsChanged()
# Fill mid-measure gaps, and find end of measure gaps by ref to measure stream
# https://github.com/cuthbertlab/music21/issues/444
v.makeRests(refStreamOrTimeRange=self.stream,
fillGaps=True,
inPlace=True,
hideRests=True)
for v in self.stream[stream.Voice]:
v.coreElementsChanged()
self.stream.coreElementsChanged()

if (self.restAndNoteCount['rest'] == 1
Expand All @@ -2588,18 +2560,16 @@ def xmlBackup(self, mxObj: ET.Element):
>>> mxBackup = EL('<backup><duration>100</duration></backup>')
>>> MP.xmlBackup(mxBackup)
>>> MP.offsetMeasureNote
0.9979
Fraction(9979, 10000)

>>> MP.xmlBackup(mxBackup)
>>> MP.offsetMeasureNote
0.0
'''
mxDuration = mxObj.find('duration')
if durationText := strippedText(mxDuration):
change = common.numberTools.opFrac(
float(durationText) / self.divisions
)
self.offsetMeasureNote -= change
change = opFrac(float(durationText) / self.divisions)
self.offsetMeasureNote = opFrac(self.offsetMeasureNote - change)
Comment on lines +2571 to +2572
Copy link
Member

Choose a reason for hiding this comment

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

This is a useful change that I'd be glad to accept in isolation.

# check for negative offsets produced by
# musicxml durations with float rounding issues
# https://github.com/cuthbertLab/music21/issues/971
Expand All @@ -2611,22 +2581,9 @@ def xmlForward(self, mxObj: ET.Element):
'''
mxDuration = mxObj.find('duration')
if durationText := strippedText(mxDuration):
change = common.numberTools.opFrac(
float(durationText) / self.divisions
)

# Create hidden rest (in other words, a spacer)
# old Finale documents close incomplete final measures with <forward>
# this will be removed afterward by removeEndForwardRest()
r = note.Rest(quarterLength=change)
r.style.hideObjectOnPrint = True
self.addToStaffReference(mxObj, r)
self.insertInMeasureOrVoice(mxObj, r)

change = opFrac(float(durationText) / self.divisions)
# Allow overfilled measures for now -- TODO(someday): warn?
self.offsetMeasureNote += change
# xmlToNote() sets None
self.endedWithForwardTag = r
self.offsetMeasureNote = opFrac(self.offsetMeasureNote + change)

def xmlPrint(self, mxPrint: ET.Element):
'''
Expand Down Expand Up @@ -2785,8 +2742,7 @@ def xmlToNote(self, mxNote: ET.Element) -> None:
self.nLast = c # update

# only increment Chords after completion
self.offsetMeasureNote += offsetIncrement
self.endedWithForwardTag = None
self.offsetMeasureNote = opFrac(self.offsetMeasureNote + offsetIncrement)

def xmlToChord(self, mxNoteList: list[ET.Element]) -> chord.ChordBase:
# noinspection PyShadowingNames
Expand Down Expand Up @@ -3578,7 +3534,7 @@ def xmlToDuration(self, mxNote, inputM21=None):
mxDuration = mxNote.find('duration')
if mxDuration is not None:
noteDivisions = float(mxDuration.text.strip())
qLen = common.numberTools.opFrac(noteDivisions / divisions)
qLen = opFrac(noteDivisions / divisions)
else:
qLen = 0.0

Expand Down Expand Up @@ -5510,7 +5466,7 @@ def parseAttributesTag(self, mxAttributes):
meth(mxSub)
# NOT to be done: directive -- deprecated since v2.
elif tag == 'divisions':
self.divisions = common.opFrac(float(mxSub.text))
self.divisions = opFrac(float(mxSub.text))
# TODO: musicxml4: for-part including part-clef
# TODO: instruments -- int if more than one instrument plays most of the time
# TODO: part-symbol
Expand Down
Loading