-
Notifications
You must be signed in to change notification settings - Fork 439
Interpret empty <forward> tags as rests on Finale documents only (derived from PR #1636, thanks @TimFelixBeyer!) #1777
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
Merged
mscuthbert
merged 7 commits into
cuthbertLab:master
from
gregchapman-dev:gregc/forwardIsHiddenRestOnlyForFinale
May 22, 2025
Merged
Changes from 6 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
3e9bfd0
Proposed solution for PR #1636. Note that I had to check md['softwar…
gregchapman-dev 1a64621
Forgot about removing the makeRests call.
gregchapman-dev 8b02acd
Update version number and fix a few tests to no longer have hidden re…
gregchapman-dev acbf273
Don't call v.coreElementsChanged on empty voices.
gregchapman-dev c6a6870
Update to include Finale and non-Finale tests from PR @1636.
gregchapman-dev 4d01fba
Merge branch 'master' into gregc/forwardIsHiddenRestOnlyForFinale
gregchapman-dev c6b7607
rename for Myke to understand, add safety
mscuthbert File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -769,6 +769,7 @@ def __init__(self): | |
| self.parts = [] | ||
|
|
||
| self.musicXmlVersion = defaults.musicxmlVersion | ||
| self.wasWrittenByFinale = False | ||
|
|
||
| def scoreFromFile(self, filename): | ||
| ''' | ||
|
|
@@ -1326,9 +1327,17 @@ def processEncoding(self, encoding: ET.Element, md: metadata.Metadata) -> None: | |
| # TODO: encoder (text + type = role) multiple | ||
| # TODO: encoding date multiple | ||
| # TODO: encoding-description (string) multiple | ||
| finaleFound: bool = False | ||
| nonFinaleFound: bool = False | ||
| for software in encoding.findall('software'): | ||
| if softwareText := strippedText(software): | ||
| if 'Finale' in softwareText: | ||
| finaleFound = True | ||
| else: | ||
| nonFinaleFound = True | ||
| md.add('software', softwareText) | ||
| if finaleFound and not nonFinaleFound: | ||
| self.wasWrittenByFinale = True | ||
|
|
||
| for supports in encoding.findall('supports'): | ||
| # todo: element: required | ||
|
|
@@ -2579,12 +2588,6 @@ def parse(self): | |
| # 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, | ||
|
gregchapman-dev marked this conversation as resolved.
|
||
| fillGaps=True, | ||
| inPlace=True, | ||
| hideRests=True) | ||
| self.stream.coreElementsChanged() | ||
|
|
||
| if (self.restAndNoteCount['rest'] == 1 | ||
|
|
@@ -2630,18 +2633,19 @@ def xmlForward(self, mxObj: ET.Element): | |
| if durationText := strippedText(mxDuration): | ||
| change = 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) | ||
| if self.parent.parent.wasWrittenByFinale: | ||
|
gregchapman-dev marked this conversation as resolved.
Outdated
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One of the few times I wish Python were Javascript -- need to check for grandparent existence... Vote YES on PEP 505 |
||
| # 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) | ||
| # xmlToNote() sets None | ||
| self.endedWithForwardTag = r | ||
|
|
||
| # Allow overfilled measures for now -- TODO(someday): warn? | ||
| self.offsetMeasureNote += change | ||
| # xmlToNote() sets None | ||
| self.endedWithForwardTag = r | ||
|
|
||
| def xmlPrint(self, mxPrint: ET.Element): | ||
| ''' | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to localize this in xmlForward() along the lines I suggested here, checking
metadataObj.software. A helper function could be extracted to do this checking.I see from your commit message you tried this and found that
setEncoding()is lossy -- we lose information about whether music21 was already in the software tag on the document -- but I think that shouldn't stop us from doing the right thing; we should just have at most one parser-level variable about that rather than one parser-level variable (and lower-order variables) about Finale-ness.Checking in xmlForward should be fast, since we can depend on the metadata object always existing at the the top of the stream (no long O(n) searches finding no metadatas anywhere):
music21/music21/musicxml/xmlToM21.py
Lines 822 to 823 in 52fc784
You'd have to change that coreInsert (as modeled in that diff I linked).
@mscuthbert do you agree that would achieve better "locality of behavior" than adding more parser attributes for edge cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming I'm understanding this correctly, I'm not sure much simplicity is gained by replacing self.wasWrittenByFinale with self.music21WasActuallyInTheDocsSoftwareMetadata (or some other better name). Maybe a rename with the existing semantics? self.applyFinaleWorkarounds, or something like that, that feels more like it is a legit parser-level variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also going to argue that performing the "loop over the software metadata" for every
<forward>element is going to be significantly more expensive than doing it once per document.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, do it once. I believe that the order of software tags written out by
music21is the order we found was most common at the time (17 years ago) for how to layer them. So we really only need to look if the top layer is Finale.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.applyFinaleWorkaroundssounds very good. Or if we want to make it very genericself.softwareWorkarounds: set[str] = {'finale'}