Skip to content

Commit

Permalink
Fix music21 <forward> tag parsing
Browse files Browse the repository at this point in the history
No longer create hidden rests while parsing forward tags in musicXML files, as this led to problems.
If there are unfilled gaps, they will be filled later by makeRests anyways if required.
  • Loading branch information
TimFelixBeyer authored Aug 20, 2023
1 parent 8baa273 commit de901e6
Showing 1 changed file with 9 additions and 55 deletions.
64 changes: 9 additions & 55 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 @@ -1760,32 +1761,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 +2210,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 +2367,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 @@ -2596,10 +2566,8 @@ def xmlBackup(self, mxObj: ET.Element):
'''
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)
# check for negative offsets produced by
# musicxml durations with float rounding issues
# https://github.com/cuthbertLab/music21/issues/971
Expand All @@ -2611,22 +2579,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 +2740,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 +3532,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 +5464,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

0 comments on commit de901e6

Please sign in to comment.