diff --git a/music21/_version.py b/music21/_version.py index 8f0c843b0..d84db94b3 100644 --- a/music21/_version.py +++ b/music21/_version.py @@ -50,7 +50,7 @@ ''' from __future__ import annotations -__version__ = '9.7.1' +__version__ = '9.7.2a4' def get_version_tuple(vv): v = vv.split('.') diff --git a/music21/base.py b/music21/base.py index dcddb37f4..d7770cadf 100644 --- a/music21/base.py +++ b/music21/base.py @@ -27,7 +27,7 @@ >>> music21.VERSION_STR -'9.7.1' +'9.7.2a4' Alternatively, after doing a complete import, these classes are available under the module "base": diff --git a/music21/musicxml/m21ToXml.py b/music21/musicxml/m21ToXml.py index b8a21c1f3..acb7a44a3 100644 --- a/music21/musicxml/m21ToXml.py +++ b/music21/musicxml/m21ToXml.py @@ -38,6 +38,7 @@ from music21 import chord from music21 import common from music21.common.enums import AppendSpanners +from music21.common.numberTools import opFrac from music21 import defaults from music21 import duration from music21 import dynamics @@ -3322,7 +3323,7 @@ def parseFlatElements( else: # if necessary, jump to end of the measure. if self.offsetInMeasure < firstPassEndOffsetInMeasure: - self.moveForward(firstPassEndOffsetInMeasure) + self.moveForward(opFrac(firstPassEndOffsetInMeasure - self.offsetInMeasure)) self.currentVoiceId = None diff --git a/music21/musicxml/test_m21ToXml.py b/music21/musicxml/test_m21ToXml.py index 0c5466c70..aaa1dbdc3 100644 --- a/music21/musicxml/test_m21ToXml.py +++ b/music21/musicxml/test_m21ToXml.py @@ -695,6 +695,60 @@ def test_instrumentDoesNotCreateForward(self): self.assertTrue(tree.findall('.//note')) self.assertFalse(tree.findall('.//forward')) + def test_writeFromSpannerAnchorsGetsMeasureEndOffsetRight(self): + ''' + Write to MusicXML from a Measure containing SpannerAnchors was not positioning + the current time offset correctly before starting the next written measure. + Now the next measure is positioned at the correct offset. + ''' + m1 = stream.Measure() + m1.append(note.Note()) + m1.append(note.Note()) + m1.append(note.Note()) + m1.append(note.Note()) + cresc = dynamics.Crescendo() + startAnchor = spanner.SpannerAnchor() + endAnchor = spanner.SpannerAnchor() + m1.insert(0.5, startAnchor) + m1.insert(1.5, endAnchor) + cresc.addSpannedElements(startAnchor, endAnchor) + m1.append(cresc) + p = stream.Part() + p.append(m1) + s = stream.Score() + s.append(p) + # write to MusicXML + tree = self.getET(s) + + # walk all the durations (notes, forwards, backups) and make sure they add up + # to where the end of the measure should be (4.0ql) + measEl = None + divisionsEl = None + for el in tree.iter(): + if el.tag == 'measure': + measEl = el + for el in measEl.iter(): + if el.tag == 'divisions': + divisionsEl = el + break + break + + self.assertIsNotNone(measEl) + self.assertIsNotNone(divisionsEl) + + divisionsInt = int(divisionsEl.text) + currOffsetQL = 0. + for el in measEl.findall('*'): + dur = el.find('duration') + if dur is not None: + durInt = int(dur.text) + durQL = common.opFrac(fractions.Fraction(durInt, divisionsInt)) + if el.tag == 'backup': + currOffsetQL = common.opFrac(currOffsetQL - durQL) + else: + currOffsetQL = common.opFrac(currOffsetQL + durQL) + self.assertEqual(currOffsetQL, 4.) + def testOutOfBoundsExpressionDoesNotCreateForward(self): ''' A metronome mark at an offset exceeding the bar duration was causing