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

Support ornament accidentals #1545

Merged

Conversation

gregchapman-dev
Copy link
Contributor

@gregchapman-dev gregchapman-dev commented Mar 26, 2023

Fixes issue #1507

@gregchapman-dev
Copy link
Contributor Author

Implementation so far: Add accidental support to Turn, GeneralMordent, Trill. Parse from/write to MusicXML.

Last step (still to do): makeAccidentals needs to add support for ornament accidentals.

@coveralls
Copy link

coveralls commented Mar 26, 2023

Coverage Status

Coverage: 93.1% (+0.03%) from 93.071% when pulling 9678517 on gregchapman-dev:gregc/ornamentAccidentals into fe306c2 on cuthbertLab:master.

@gregchapman-dev
Copy link
Contributor Author

Yeah, I know, I have a bunch more tests to write.

@gregchapman-dev
Copy link
Contributor Author

@mscuthbert @jacobtylerwalls Can I get some review of this implementation (while I work on makeAccidentals)?

@gregchapman-dev
Copy link
Contributor Author

gregchapman-dev commented Mar 27, 2023

Hmmm... makeAccidentals needs to deal with Pitches, not just Accidentals. So... Maybe instead of ornament.accid/lowerAccid/upperAccid being Accidentals, I should just have them be strings ('flat', 'sharp', etc as in MusicXML). Then parsers and makeAccidentals can do ornament.resolveOtherPitches(srcPitch), which will actually set up an internal list of "other" pitches implied by the ornament (which will be returned by ornament.otherPitches, and included in stream.pitches). The accidentals on those pitches would mean exactly what they mean on note/chord pitches. And those accidentals might be set because of ornament.getSize(srcPitch) and current key, even if accid/upperaccid/lowerAccid is not set (but size is, or class name means something). And makeAccidentals can pay attention to them, and set displayStatus on them (parsers will set displayStatus on them too, of course).

resolveOtherPitches(srcPitch) might need a different verb: realize, setup, create, compute, figureOut, make, or something like that.

@gregchapman-dev
Copy link
Contributor Author

OK, initial implementation of everything is done. I need to write a bunch of tests (especially for makeAccidentals stuff), but this should be reviewable now.

Copy link
Member

@mscuthbert mscuthbert left a comment

Choose a reason for hiding this comment

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

Great work! Love it! Sorry that most individual comments are on the things I dislike, but it's a significant contribution.

Things requested to change are:

  1. please document interface before requesting review next time (I'd prefer to review an empty interface w/o implementation rather than an implementation without docs). It slows down the review immensely to have to guess what a routine does and how it handles corner cases.
  2. otherPitches doesn't have any meaning to me -- what is this?
  3. getSize -- issues with naming and the notion that the size returned will later be modified by key context etc. -- one routine is preferred that takes in all the information it needs to spit out the exactly right interval. Try to think of the use case where someone want to measure an ornament w/o realizing it.
  4. don't change the behavior of Stream.pitches.
  5. getters cannot raise exceptions. See note for reason.
  6. makeAccidentals is already way too long and complicated -- use helper functions to remove redundancy.

music21/expressions.py Outdated Show resolved Hide resolved
music21/expressions.py Outdated Show resolved Hide resolved
music21/expressions.py Show resolved Hide resolved
music21/expressions.py Outdated Show resolved Hide resolved
music21/expressions.py Outdated Show resolved Hide resolved
music21/expressions.py Outdated Show resolved Hide resolved
music21/stream/base.py Outdated Show resolved Hide resolved
music21/stream/base.py Outdated Show resolved Hide resolved
music21/stream/base.py Outdated Show resolved Hide resolved
music21/stream/base.py Outdated Show resolved Hide resolved
@gregchapman-dev
Copy link
Contributor Author

Thank you for the quick review (and sorry for the lack of documentation). I agree with nearly all of what you said, and the rest just raises questions, not disagreement. Hoping to find some time to work through most of this tomorrow.

@gregchapman-dev
Copy link
Contributor Author

@mscuthbert I think I'm done here, ready for your re-review. Please do validate that my changes to pitch.py's test expectations are correct (that's the very last commit above).

music21/stream/base.py Outdated Show resolved Hide resolved
music21/test/test_expressions.py Outdated Show resolved Hide resolved
music21/test/test_expressions.py Outdated Show resolved Hide resolved
…tead of in the pitch.accidental = float setter (to minimize risk). Add Unpitched to the typing for all the realize-related methods.
…= float setter as out-of-scope and too risky.
@gregchapman-dev
Copy link
Contributor Author

gregchapman-dev commented Apr 8, 2023

I have removed the microtones-to-quarter-tones operation that I had added to pitch.accidental's float setter, and added it instead to the resolveOrnamentalPitches methods in Trill/Turn/GeneralMordent. Note that I had to work around a different bug in transpose where microtones get completely lost if you transpose in place (that I have written an issue for: #1553). I also addressed Jacob's review comments.

@gregchapman-dev
Copy link
Contributor Author

gregchapman-dev commented Apr 11, 2023

I've decided I'm not done yet. I've been playing with my Humdrum writer, making it write from these new Ornament objects, and I'm finding that sometimes the Ornament ends up with the wrong size, due to the fact that the ornamentalPitch was modified by an accidental on a note earlier in the measure (and no-one knows, so it ends up not modified). This is actually a bug in my Humdrum importer, where I know what the accidental should be, but I don't set accidentalName because I don't want to force it to be visible. But then getSize doesn't know about that accidental, so it gets the size wrong (and therefore the ornamentalPitch comes out wrong, too). So I think @mscuthbert's idea earlier (that ornament.accidentalName should actually be an Accidental object instead of a name string) is correct, so my parser can set the accidental, and then set that accidental's displayStatus to True if it is supposed to be visible. Then resolveOrnamentalPitches will need to copy that displayStatus to the resolved ornamentalPitch.accidental. (And all of this is doubled for Turns, where there are two ornamentalPitches, two accidentalNames, etc).

…). Same with Turn.upperAccidentalName and lowerAccidentalName.
@gregchapman-dev
Copy link
Contributor Author

OK, I've made that change. I think I might be done here.

@mscuthbert
Copy link
Member

Doing re-review now. Even before looking at the code, just want to say "Excellent work!" and thank you.

Copy link
Member

@mscuthbert mscuthbert left a comment

Choose a reason for hiding this comment

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

Great work! You'll be relieved to see that there are no conceptual changes requested, nothing about "wouldn't this be better if .accidental took a NeoRiemannianTransformation instead?" etc. :-)

There are some places where I'd like refactors for clarity (and making Stream.base not so darn big) and some docs that are missing, etc.

There are some significant typing improvements that can be done after this is merged.

Thanks!

music21/expressions.py Outdated Show resolved Hide resolved
music21/expressions.py Show resolved Hide resolved
music21/expressions.py Show resolved Hide resolved
music21/expressions.py Outdated Show resolved Hide resolved
music21/expressions.py Outdated Show resolved Hide resolved
music21/pitch.py Outdated Show resolved Hide resolved
music21/stream/base.py Outdated Show resolved Hide resolved
music21/stream/base.py Outdated Show resolved Hide resolved
music21/stream/base.py Outdated Show resolved Hide resolved
music21/test/test_expressions.py Outdated Show resolved Hide resolved
@mscuthbert
Copy link
Member

The next review should be light enough that I'll get it on a mid-week review schedule when it comes in. Needed to wait for a holiday for this one.

@gregchapman-dev
Copy link
Contributor Author

Thank you for the thorough review! I'll make these changes over the next few days.

@gregchapman-dev
Copy link
Contributor Author

I believe I have addressed all @mscuthbert's latest review comments.

Comment on lines 6868 to 6834
self.makeOrnamentalAccidentals(
makeNotation.makeOrnamentalAccidentals(
Copy link
Member

Choose a reason for hiding this comment

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

Oh! it didn't even need self -- then definitely best not to live on Stream!

Comment on lines +1725 to +1728
from music21.stream import Stream
post = []
for e in s.elements:
if isinstance(e, Stream):
Copy link
Member

Choose a reason for hiding this comment

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

this can be done without the import with if 'Stream' in e.classes which is why .classes is still there. NBD!

Copy link
Member

@mscuthbert mscuthbert left a comment

Choose a reason for hiding this comment

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

Looks great! Thanks for this amazing and helpful contribution to music21!

@mscuthbert mscuthbert merged commit 2526b68 into cuthbertLab:master Apr 19, 2023
@gregchapman-dev
Copy link
Contributor Author

Thanks for taking it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for rendering (a.k.a. writing MusicXML) trills/mordents/turns with accidentals
4 participants