-
Notifications
You must be signed in to change notification settings - Fork 403
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
FretBend update #1580
base: master
Are you sure you want to change the base?
FretBend update #1580
Conversation
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.
Well on its way! Ask around if you need help in writing tests, and for help on why music21's system of intervals is more complex than musicxml's.
(the changes to note.Note are rejected, so please remove them, thanks!)
music21/articulations.py
Outdated
@@ -84,6 +84,7 @@ | |||
from music21.common.classTools import tempAttribute | |||
from music21 import environment | |||
from music21 import style | |||
from music21 import interval |
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.
alphabetical order (existing mistake with style before spanner can be fixed in this PR also)
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.
and if this is added it needs to be removed from they TYPE_CHECKING import below (but I don't think it will need to be, see below)
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.
Done with 3cf71e0
music21/articulations.py
Outdated
release: t.Optional[float] | ||
withBar: t.Optional[bool] | ||
|
||
def __init__(self, number=0, preBend=False, release=None, withBar=None, **keywords): |
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.
number is too generic a term -- and we usually reserve the term for identifying which of multiple simultaneous items (like slur number=1 vs slur number=2). Are fret-bends always chromatic intervals? it seems like you can also have a diatonic interval of a fretbend. hence why bendAlter was typed as IntervalBase not ChromaticInterval. It seems better to have bendAlter as described above interval.IntervalBase | None = None
where None indicates unspecified. Having a default of 0, becoming ChromaticInterval(0) indicates that this fretBend does not alter the pitch at all, which would be a very strange fret bend. This would also make it so that interval.py does not need to be imported here.
The other keywords should be typed in the __init__
declaration in addition to the class.
We no longer use t.Optional[X]
in the code base. Use X | None
instead.
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.
Changed with d63c78a
music21/articulations.py
Outdated
bend indication for fretted instruments | ||
|
||
bend in musicxml | ||
|
||
number is the interval of the bend in number of semitones, bend-alter in musicxml | ||
preBend indicates wether the note is prebended or not | ||
release is the offset value for releasing the bend, if Any | ||
withBar indicates if the bend is done using a whammy bar movement |
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.
Capitalization and use asterisks and line breaks for documentation -- run documentation/build.py and look at the difference between the docs produced here and elsewhere in the system.
preBend indicates wether the note is prebended or not
-- I don't know what prebended means.
what is release measured in? Is it an offset (which is relative to the start of the Measure/stream)? That doesn't sound right. Is it a quarterLength measured from the start of the note? or from the end of the note.
withBar doc is fine.
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've tried to improve it in 03e6117
Let me know if that seems good to you
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.
tests are on their way
music21/musicxml/m21ToXml.py
Outdated
bendAlterSubElement = SubElement(mxTechnicalMark, 'bend-alter') | ||
bendAlterSubElement.text = str(articulationMark.bendAlter.semitones) |
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.
this has enough ifs that it should be brought out as its own small method that allows for easier documentation and testing. articulationMark will probably need to be cast as a FretBend.
We can solve the case of GenericInterval
later, which doesn't have .semitones. Perhaps it always needs to be a full Interval object or None.
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.
New submethod added in 85cc6a8
Now I indeed have the problem that I need to retrieve the number of semitones. I could probably do something with GenericInterval.value
but I don't know how to do the conversion properly
music21/musicxml/m21ToXml.py
Outdated
releaseSubElement = SubElement(mxTechnicalMark, 'release') | ||
releaseSubElement.set('offset', str(articulationMark.release)) |
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.
musicxml's definition of offset is very different from music21's and (despite me now being editor of the musicxml spec) we shouldn't use musicxml offsets anywhere in music21 -- these need to be converted according to the current divisionsPerQuarter setting.
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.
should work with d93af6d though I need to write the tests
music21/musicxml/xmlToM21.py
Outdated
@@ -3881,11 +3881,18 @@ def xmlTechnicalToArticulation(self, mxObj): | |||
if tag in ('heel', 'toe'): | |||
if mxObj.get('substitution') is not None: | |||
tech.substitution = xmlObjects.yesNoToBoolean(mxObj.get('substitution')) | |||
if tag == 'bend': | |||
tech.bendAlter = interval.Interval(int(mxObj.find('bend-alter').text)) |
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.
this has too many potentials for crashing the processing. What if there is no bend-alter or no bend-alter.text? check that before typing to assign.
(Better to call out into a separate sub-method. This method was designed just for one-to-two lines per technical
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.
sub-method and bend-alter check in 2e1ea43
music21/musicxml/xmlToM21.py
Outdated
if mxObj.find('pre-bend') is not None: | ||
tech.preBend = True | ||
if mxObj.find('release') is not None: | ||
tech.release = int(mxObj.find('release').get('offset')) |
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.
as above -- offset needs to be converted to music21 quarterLengths.
except clause without Try.
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.
Both fixed with 58eeb04
music21/musicxml/xmlToM21.py
Outdated
tech.release = int(mxObj.find('release').get('offset')) | ||
except TypeError: | ||
# offset is not mandatory | ||
tech.release = 0 |
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.
release is a float. needs to be 0.0
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.
also fixed with 58eeb04
music21/note.py
Outdated
from music21 import articulations | ||
|
||
if t.TYPE_CHECKING: | ||
from music21 import articulations |
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.
as above -- alphabetical and remove from type-checking if imported at top. (but won't be necessary).
music21/note.py
Outdated
@property | ||
def string(self): |
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.
this new property and all properties below are rejected. Note is the most common object in music21 and users who are not working with guitar music will never use these routines but will need to figure out what they mean in the docs. (we don't have "accent" or "staccato" etc. either) -- and parallel routines would need to be added to Chord, etc.
In general for any mature open-source project, don't make additions or substantive changes to core parts of the system without a prior discussion.
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.
oops sorry about that, they were just for my personal convenience. Removed with 0b2074f
Hello -- just as a note (from the music21list Google Group) I'm taking a sabbatical from reviewing music21 issues and PRs until at least Jan 1, 2024, so this PR will be deferred until then. |
Hello Michael, sorry for not being able to finish this PR sooner. Enjoy your sabbatical! Hopefully my PR will be ready for approval when you come back 🤞 |
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.
Hi! It's again a Request Changes, but they're really minimal changes and I think this is very close to getting in. :-) CONGRATS on the amazing work so far and for putting up with a sabbatical that interrupted it!
>>> fb.release | ||
0.5 | ||
''' | ||
bendAlter: interval.IntervalBase | None |
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.
since we need to have a number of semitones, maybe make this interval.Interval | interval.ChromaticInterval | None
so that if it's not None it definitely has a number of semitones.
I'm not a guitar player so forgive me, but is there a concept of an indefinitely sized bend upwards or downwards? where the direction is known but not the size? (this could be something to put in later)
|
||
WithBar indicates if the bend is done using a whammy bar movement. Defaults to False. | ||
|
||
>>> fb = articulations.FretBend(bendAlter=interval.ChromaticInterval(-2), release=0.5) |
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.
add number to this to show that it appears in the fb representation below.
release: float | None | ||
withBar: bool | ||
|
||
def __init__(self, number=0, bendAlter=None, preBend=False, release=None, withBar=False, **keywords): |
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.
type needs to be repeated here. Also make all but number keyword only so people don't have the remember the order.
def __init__(
self,
number: int = 0,
*,
bendAlter: interval.Interval | interval.ChromaticInterval | None = None,
preBend: bool = False,
release: OffsetQL | None = None,
withBar: bool = False,
**keywords
):```
withBar: bool | ||
|
||
def __init__(self, number=0, bendAlter=None, preBend=False, release=None, withBar=False, **keywords): | ||
super().__init__(**keywords) |
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.
bug. needs to be super().__init__(number=number, **keywords)
# musicxml expects a number of semitones but not sure how to get it | ||
# from a GeneralInterval | ||
pass |
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.
now with the change in typing above this can be possible.
# hammer-on and pull-off not implemented because handled | ||
# in method objectAttachedSpannersToTechnicals of m21ToXml.py | ||
# ('hammer-on', articulations.HammerOn), | ||
# ('pull-off', articulations.PullOff), | ||
# bend not implemented because it needs many subcomponents | ||
# ('bend', articulations.FretBend), | ||
('bend', articulations.FretBend), |
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.
this line appears to be added twice? (see line 70)
self.setPlacement(mxObj, tech) | ||
return tech | ||
else: | ||
environLocal.printDebug(f'Cannot translate {tag} in {mxObj}.') | ||
return None | ||
|
||
@staticmethod |
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.
Not going to be able to be a staticmethod -- see below.
''' | ||
bendAlter: interval.IntervalBase | None | ||
preBend: bool | ||
release: float | None |
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.
instead of float, type as OffsetQL which is float|Fraction
Music21 represents 1/3 as Fraction(1, 3) not 0.3333333428 as an inexact float.
# divisions relative to the current note. | ||
releaseSubElement = SubElement(mxh, 'release') | ||
quarterLengthValue = bend.release | ||
divisionsValue = defaults.divisionsPerQuarter * quarterLengthValue |
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.
divisionsValue = int(defaults.divisionsPerQuarter * quarterLengthValue)
Musicxml "prefers" that offset be an integer:
https://www.w3.org/2021/06/musicxml40/musicxml-reference/data-types/divisions/
divisions = float(mxh.find('release').get('offset')) | ||
bend.release = divisions / defaults.divisionsPerQuarter |
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.
In writing to musicxml, music21 uses defaults.divisionsPerQuarter. But in reading from musicxml, we need to use the divisions defined earlier in the score, which is in self.divisions on the measure parser.
Write
bend.release = opFrac(divisions / defaults.divisionsPerQuarter)
opFrac will convert it to Fraction(1, 3) if it's released within a triplet, etc. and leave it alone as 0.5 if it's a regular eighth note, etc.
Fixes #1471
Hi! First PR so I expect a few things to be added/modified but here's a first draft.
I've updated the
FretBend
articulation so that it includes the musicXML propertiesbendAlter
,preBend
andrelease
.It goes round from musicXML to m21 back to mXML.
I've not written any tests yet because I'm not sure what to add. Let me know of what could be good to add!
as a PR newbie, I also have some irrelevant commits that I manually reverted. If that's a problem I'll try to clean that up.