diff --git a/music21/_version.py b/music21/_version.py index eed25cb59..d45d6c0cd 100644 --- a/music21/_version.py +++ b/music21/_version.py @@ -47,7 +47,7 @@ ''' from __future__ import annotations -__version__ = '11.0.0b6' +__version__ = '11.0.0b7' def get_version_tuple(vv): v = vv.split('.') diff --git a/music21/base.py b/music21/base.py index b5fc735fb..85ae8bbe7 100644 --- a/music21/base.py +++ b/music21/base.py @@ -26,7 +26,7 @@ >>> music21.VERSION_STR -'11.0.0b6' +'11.0.0b7' Alternatively, after doing a complete import, these classes are available under the module "base": diff --git a/music21/musicxml/helpers.py b/music21/musicxml/helpers.py index 804cb0c7c..9722295cc 100644 --- a/music21/musicxml/helpers.py +++ b/music21/musicxml/helpers.py @@ -151,6 +151,47 @@ def insertBeforeElements(root, insert, tagList=None): root.insert(min(insertIndices), insert) +def insertAfterElements(root, insert, tagList=None): + # noinspection PyShadowingNames + ''' + Insert element `insert` into element `root` directly after the last + instance of any child tag given in `tagList`. Insert at the beginning + if `tagList` is `None` or no instance is found. + + >>> from xml.etree.ElementTree import fromstring as EL + >>> from music21.musicxml.helpers import insertAfterElements, dump + >>> root = EL('1') + >>> insert = EL('') + + >>> insertAfterElements(root, insert, tagList=['grace']) + >>> dump(root) + + + + 1 + + + Insert at the beginning when nothing in `tagList` matches: + + >>> insert2 = EL('') + >>> insertAfterElements(root, insert2, tagList=['bar']) + >>> dump(root) + + + + + 1 + + ''' + insertIndex = 0 + if tagList: + # Iterate children only, not grandchildren + for i, child in enumerate(root.findall('*')): + if child.tag in tagList: + insertIndex = i + 1 + root.insert(insertIndex, insert) + + def measureNumberComesBefore(mNum1: str, mNum2: str) -> bool: ''' Determine whether `measureNumber1` strictly precedes diff --git a/music21/musicxml/m21ToXml.py b/music21/musicxml/m21ToXml.py index 5c525dace..5098428eb 100644 --- a/music21/musicxml/m21ToXml.py +++ b/music21/musicxml/m21ToXml.py @@ -4622,6 +4622,22 @@ def unpitchedToXml(self, quarter + + must come before , even when there is + no , as on a grace note in a voice: + + >>> MEX.currentVoiceId = 1 + >>> mxUnpitched = MEX.unpitchedToXml(graceUp) + >>> MEX.dump(mxUnpitched) + + + + D + 5 + + 1 + quarter + ''' mxNote = self.noteToXml(up, noteIndexInChord=noteIndexInChord, chordParent=chordParent) @@ -4629,7 +4645,14 @@ def unpitchedToXml(self, _setTagTextFromAttribute(up, mxUnpitched, 'display-step') _setTagTextFromAttribute(up, mxUnpitched, 'display-octave') - helpers.insertBeforeElements(mxNote, mxUnpitched, tagList=['duration', 'type']) + # within , only , , and may precede + # , so insert after those rather than trying to list + # everything that can follow it (#1732) + helpers.insertAfterElements( + mxNote, + mxUnpitched, + tagList=['grace', 'cue', 'chord'] + ) return mxNote diff --git a/music21/musicxml/test_m21ToXml.py b/music21/musicxml/test_m21ToXml.py index 5e0e47fc4..fc98f8798 100644 --- a/music21/musicxml/test_m21ToXml.py +++ b/music21/musicxml/test_m21ToXml.py @@ -229,6 +229,21 @@ def testLowVoiceNumbers(self): xmlOut = self.getXml(m) self.assertIn('hello', xmlOut) + def testUnpitchedGraceInVoice(self): + ''' + must precede even on a grace note, which has + no element to insert before. + ''' + # https://github.com/cuthbertLab/music21/issues/1732 + grace = note.Unpitched(duration=duration.GraceDuration()) + v1 = stream.Voice([grace]) + v2 = stream.Voice([note.Note()]) + m = stream.Measure([v1, v2]) + + tree = self.getET(m) + mxNote = tree.find('part/measure/note') + self.assertEqual([el.tag for el in mxNote], ['grace', 'unpitched', 'voice', 'type']) + def testVoiceNumberOffsetsThreeStaffsInGroup(self): n1_1 = note.Note() v1_1 = stream.Voice([n1_1])