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

FretBend update #1580

Draft
wants to merge 79 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
03fdb47
:arrow_heading_up: bend export
adhooge Nov 21, 2022
aca37e2
add some properties to note.Note
adhooge Nov 23, 2022
7118189
fix missing import
adhooge Dec 2, 2022
766a113
`FretBend`: update preBend type
adhooge Jan 12, 2023
17bc30d
`FretBend`: update Release type
adhooge Jan 12, 2023
4bcdffc
noteToNotations: export string fret art in chord
Feb 28, 2023
274c904
new changes
May 22, 2023
0fdac57
Merge remote-tracking branch 'upstream/master'
May 22, 2023
6d778f1
read bends
May 23, 2023
7e6ced8
restore README
May 23, 2023
ef52fe0
single quotes
May 23, 2023
e3f88ee
typing withBar
May 23, 2023
f1eae7d
python3.9 compatibility
May 23, 2023
22d07da
remove useless files
May 23, 2023
c157a53
bendAlter typing
May 23, 2023
67540ea
rm useless file
May 23, 2023
34ecc42
restore file
May 23, 2023
13ac21f
fix some conflicts
Mar 18, 2024
9c3b1a3
add some properties to note.Note
adhooge Nov 23, 2022
0839230
fix missing import
adhooge Dec 2, 2022
cd534fe
finish rebase
Mar 18, 2024
b1c9277
`FretBend`: update Release type
adhooge Jan 12, 2023
caa8bd2
noteToNotations: export string fret art in chord
Feb 28, 2023
a21d3f6
new changes
May 22, 2023
7855e26
read bends
May 23, 2023
b95c2c0
restore README
May 23, 2023
97297be
single quotes
May 23, 2023
3d5092a
typing withBar
May 23, 2023
e45f7b8
python3.9 compatibility
May 23, 2023
4f07cbb
bendAlter typing
May 23, 2023
97e33a0
restore file
May 23, 2023
edf7934
add mute articulation
Aug 24, 2023
27974be
Merge branch 'pr-bend' of github.com:adhooge/music21 into pr-bend
Mar 18, 2024
3cf71e0
alphabetical imports
Mar 18, 2024
d63c78a
rework types and default values
Mar 18, 2024
03e6117
FretBend: rework documentation
Mar 18, 2024
85cc6a8
separate method for FretBend
Mar 18, 2024
c11ce14
comment from musicXML doc
Mar 18, 2024
d93af6d
release to divisions
Mar 18, 2024
2e1ea43
make setBend a sub-method
Mar 18, 2024
58eeb04
convert offset to quarter length
Mar 18, 2024
0b2074f
remove my properties
Mar 18, 2024
f215c8c
FretBend: add tests
Mar 18, 2024
e4499dc
Merge branch 'cuthbertLab:master' into pr-bend
adhooge Aug 13, 2024
0ba3f33
fix some conflicts
Mar 18, 2024
32d5135
add some properties to note.Note
adhooge Nov 23, 2022
030935a
fix missing import
adhooge Dec 2, 2022
a40c9d6
finish rebase
Mar 18, 2024
4938f1e
`FretBend`: update Release type
adhooge Jan 12, 2023
ded5058
noteToNotations: export string fret art in chord
Feb 28, 2023
bb8ab1d
new changes
May 22, 2023
4c5d96d
read bends
May 23, 2023
6646a04
restore README
May 23, 2023
640f051
single quotes
May 23, 2023
5ab284d
typing withBar
May 23, 2023
30c2a85
python3.9 compatibility
May 23, 2023
8262559
bendAlter typing
May 23, 2023
e36d0ff
restore file
May 23, 2023
5cf8b7d
add mute articulation
Aug 24, 2023
63b9df5
:arrow_heading_up: bend export
adhooge Nov 21, 2022
5dc55d9
`FretBend`: update preBend type
adhooge Jan 12, 2023
11b6e7e
`FretBend`: update Release type
adhooge Jan 12, 2023
ba04559
noteToNotations: export string fret art in chord
Feb 28, 2023
b2bd258
single quotes
May 23, 2023
04d369e
typing withBar
May 23, 2023
8ef43f0
python3.9 compatibility
May 23, 2023
43e9277
bendAlter typing
May 23, 2023
38c0d61
restore file
May 23, 2023
adc65e3
alphabetical imports
Mar 18, 2024
1420a42
rework types and default values
Mar 18, 2024
3ee1fba
FretBend: rework documentation
Mar 18, 2024
2773315
separate method for FretBend
Mar 18, 2024
467317b
comment from musicXML doc
Mar 18, 2024
dae3be6
release to divisions
Mar 18, 2024
5c3b870
make setBend a sub-method
Mar 18, 2024
60fcfed
convert offset to quarter length
Mar 18, 2024
2c41586
remove my properties
Mar 18, 2024
96782de
FretBend: add tests
Mar 18, 2024
e7e0db3
Merge branch 'pr-bend' of github.com:adhooge/music21 into pr-bend
adhooge Aug 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 42 additions & 7 deletions music21/articulations.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,9 @@
from music21 import common
from music21.common.classTools import tempAttribute
from music21 import environment
from music21 import style
from music21 import spanner
from music21 import style

if t.TYPE_CHECKING:
from music21 import interval


environLocal = environment.Environment('articulations')
Expand Down Expand Up @@ -582,10 +580,47 @@ class PullOff(spanner.Spanner, TechnicalIndication):
pass

class FretBend(FretIndication):
bendAlter: interval.IntervalBase|None = None
preBend: t.Any = None
release: t.Any = None
withBar: t.Any = None
'''
Bend indication for fretted instruments

Bend in musicxml

Number is an identifier for the articulation. Defaults to 0.

BendAlter is the interval of the bend in number of semitones,
bend-alter in musicxml. Defaults to None.

PreBend indicates if the string is bent before
the onset of the note. Defaults to False.

Release is the quarterLength value from the start
of the note for releasing the bend, if Any. Defaults to None.

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)
Copy link
Member

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.

>>> fb
<music21.articulations.FretBend 0>
>>> fb.preBend
False
>>> fb.withBar
False
>>> fb.bendAlter
<music21.interval.ChromaticInterval -2>
>>> fb.release
0.5
'''
bendAlter: interval.IntervalBase | None
Copy link
Member

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)

preBend: bool
release: float | None
Copy link
Member

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.

withBar: bool

def __init__(self, number=0, bendAlter=None, preBend=False, release=None, withBar=False, **keywords):
Copy link
Member

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
):```

super().__init__(**keywords)
Copy link
Member

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)

self.bendAlter = bendAlter
self.preBend = preBend
self.release = release
self.withBar = withBar

class FretTap(FretIndication):
pass
Expand Down
56 changes: 52 additions & 4 deletions music21/musicxml/m21ToXml.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
from music21.musicxml import helpers
from music21.musicxml.partStaffExporter import PartStaffExporterMixin
from music21.musicxml import xmlObjects
from music21.musicxml.xmlObjects import MusicXMLExportException
from music21.musicxml.xmlObjects import MusicXMLExportException, booleanToYesNo
from music21.musicxml.xmlObjects import MusicXMLWarning

environLocal = environment.Environment('musicxml.m21ToXml')
Expand Down Expand Up @@ -5411,11 +5411,18 @@ def articulationToXmlTechnical(self, articulationMark: articulations.Articulatio
>>> mxOther = MEX.articulationToXmlTechnical(g)
>>> MEX.dump(mxOther)
<other-technical>unda maris</other-technical>

Same with technical marks not yet supported.
TODO: support HammerOn, PullOff, Hole, Arrow.

>>> h = articulations.HammerOn()
>>> mxOther = MEX.articulationToXmlTechnical(h)
>>> MEX.dump(mxOther)
<other-technical />
'''
# these technical have extra information
# TODO: hammer-on
# TODO: pull-off
# TODO: bend
# TODO: hole
# TODO: arrow
musicXMLTechnicalName = None
Expand All @@ -5427,7 +5434,7 @@ def articulationToXmlTechnical(self, articulationMark: articulations.Articulatio
musicXMLTechnicalName = 'other-technical'

# TODO: support additional technical marks listed above
if musicXMLTechnicalName in ('bend', 'hole', 'arrow'):
if musicXMLTechnicalName in ('hole', 'arrow'):
musicXMLTechnicalName = 'other-technical'

mxTechnicalMark = Element(musicXMLTechnicalName)
Expand Down Expand Up @@ -5461,7 +5468,8 @@ def articulationToXmlTechnical(self, articulationMark: articulations.Articulatio
if t.TYPE_CHECKING:
assert isinstance(articulationMark, articulations.FretIndication)
mxTechnicalMark.text = str(articulationMark.number)

if musicXMLTechnicalName == 'bend':
self.setBend(mxTechnicalMark, articulationMark)
# harmonic needs to check for whether it is artificial or natural, and
# whether it is base-pitch, sounding-pitch, or touching-pitch
if musicXMLTechnicalName == 'harmonic':
Expand All @@ -5477,6 +5485,46 @@ def articulationToXmlTechnical(self, articulationMark: articulations.Articulatio
# mxArticulations.append(mxArticulationMark)
return mxTechnicalMark

@staticmethod
def setBend(mxh: Element, bend: articulations.FretBend) -> None:
Copy link
Member

Choose a reason for hiding this comment

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

looks great!

'''
Sets the bend-alter SubElement and the pre-bend,
release and with-bar SubElements when present.

Called from articulationToXmlTechnical

>>> MEXClass = musicxml.m21ToXml.MeasureExporter

>>> a = articulations.FretBend()

>>> from xml.etree.ElementTree import Element
>>> mxh = Element('bend')

>>> MEXClass.setBend(mxh, a)
>>> MEXClass.dump(mxh)
<bend>
<bend-alter></bend-alter>
</bend>
'''
bendAlterSubElement = SubElement(mxh, 'bend-alter')
alter = bend.bendAlter
if alter is not None:
# musicxml expects a number of semitones but not sure how to get it
# from a GeneralInterval
pass
Comment on lines +5512 to +5514
Copy link
Member

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.

if bend.preBend:
SubElement(mxh, 'pre-bend')
if bend.release is not None:
# Specifies where the release starts in terms of
# divisions relative to the current note.
releaseSubElement = SubElement(mxh, 'release')
quarterLengthValue = bend.release
divisionsValue = defaults.divisionsPerQuarter * quarterLengthValue
Copy link
Member

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/

releaseSubElement.set('offset', str(divisionsValue))
if bend.withBar is not None:
withBarSubElement = SubElement(mxh, 'with-bar')
withBarSubElement.text = str(bend.withBar)

@staticmethod
def setHarmonic(mxh: Element, harm: articulations.StringHarmonic) -> None:
# noinspection PyShadowingNames
Expand Down
4 changes: 2 additions & 2 deletions music21/musicxml/xmlObjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,12 @@
('stopped', articulations.Stopped),
('snap-pizzicato', articulations.SnapPizzicato),
('string', articulations.StringIndication),
('bend', articulations.FretBend),
# 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),
Copy link
Member

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)

('tap', articulations.FretTap),
('fret', articulations.FretIndication),
('heel', articulations.OrganHeel),
Expand Down
19 changes: 17 additions & 2 deletions music21/musicxml/xmlToM21.py
Original file line number Diff line number Diff line change
Expand Up @@ -3861,17 +3861,32 @@ 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':
self.setBend(mxObj, tech)
# TODO: <bend> attr: accelerate, beats, first-beat, last-beat, shape (4.0)
# TODO: <bent> sub-elements: bend-alter, pre-bend, with-bar, release
# TODO: musicxml 4: release sub-element as offset attribute


self.setPlacement(mxObj, tech)
return tech
else:
environLocal.printDebug(f'Cannot translate {tag} in {mxObj}.')
return None

@staticmethod
Copy link
Member

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.

def setBend(mxh, bend):
alter = mxh.find('bend-alter')
if alter is not None:
if alter.text is not None:
bend.bendAlter = interval.Interval(int(alter.text))
if mxh.find('pre-bend') is not None:
bend.preBend = True
if mxh.find('release') is not None:
try:
divisions = float(mxh.find('release').get('offset'))
bend.release = divisions / defaults.divisionsPerQuarter
Comment on lines +3885 to +3886
Copy link
Member

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.

except (ValueError, TypeError) as unused_err:
bend.release = 0.0

@staticmethod
def setHarmonic(mxh, harm):
'''
Expand Down