Skip to content
Merged
Show file tree
Hide file tree
Changes from 97 commits
Commits
Show all changes
109 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
d768834
fix bendAlter typing
adhooge Aug 18, 2024
099df36
pass number to superclass and test it
adhooge Aug 18, 2024
f74771b
fix FretBend __init__ signature
adhooge Aug 18, 2024
95e00d7
export bendAlter
adhooge Aug 18, 2024
2afc440
remove comment
adhooge Aug 18, 2024
f7cb7c2
only export withBar when True
adhooge Aug 18, 2024
f0d27f8
type release as OffsetQL
adhooge Aug 18, 2024
d651fbd
make offset an int for musicXML
adhooge Aug 18, 2024
15eadd1
use self.divisions and opFrac
adhooge Aug 18, 2024
de0c20d
parse bendAlter as float to support microtones
adhooge Aug 18, 2024
ec04053
linting: remove unused imports
adhooge Aug 18, 2024
28b8372
fix withBar type
adhooge Aug 19, 2024
c5b04cc
add tests
adhooge Aug 19, 2024
daa6848
add tests
adhooge Aug 19, 2024
b0b66aa
Merge branch 'cuthbertLab:master' into pr-bend
adhooge May 4, 2025
e3749de
Merge branch 'pr-bend' of github.com:adhooge/music21 into pr-bend
adhooge May 5, 2025
4c585ea
use number as a positional argument
adhooge May 5, 2025
91e9f84
remove duplicate technical_mark
adhooge May 5, 2025
a79a6dc
fix doctests
adhooge May 13, 2025
51e8ee5
fix some writing
adhooge May 14, 2025
d4e175d
update test
adhooge May 14, 2025
9879148
unindent arguments
adhooge May 14, 2025
b39ea71
import interval for pylint
adhooge May 14, 2025
243f3b6
import OffsetQL for pylint
adhooge May 14, 2025
9a17ec8
remove trailing whitespace
adhooge May 14, 2025
3fc5849
fix indentation for flake8
adhooge May 14, 2025
d1ff663
add FretBend type checking
adhooge May 14, 2025
d021fed
Merge branch 'cuthbertLab:master' into pr-bend
adhooge May 14, 2025
2447e4e
minor improvements on the documentation
adhooge May 14, 2025
7e8a800
Merge branch 'pr-bend' of github.com:adhooge/music21 into pr-bend
adhooge May 14, 2025
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
60 changes: 52 additions & 8 deletions music21/articulations.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,18 +76,15 @@
'''
from __future__ import annotations

import typing as t
import unittest

from music21 import base
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 +579,57 @@ 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 what whammy bar movement is used, if any.
MusicXML supports 'scoop' or 'dip'. Defaults to None.

>>> fb = articulations.FretBend(1,bendAlter=interval.ChromaticInterval(-2),release=0.5)
>>> fb
<music21.articulations.FretBend 1>
>>> fb.preBend
False
>>> fb.withBar

>>> fb.bendAlter
<music21.interval.ChromaticInterval -2>
>>> fb.release
0.5
'''
bendAlter: interval.Interval | interval.ChromaticInterval | None
preBend: bool
release: OffsetQL | None
withBar: str | None

def __init__(
self,
number: int = 0,
*,
bendAlter: interval.Interval | interval.ChromaticInterval | None = None,
preBend: bool = False,
release: OffsetQL | None = None,
withBar: str | None = None,
**keywords
):
super().__init__(number=number, **keywords)
self.bendAlter = bendAlter
self.preBend = preBend
self.release = release
self.withBar = withBar

class FretTap(FretIndication):
pass
Expand Down
71 changes: 68 additions & 3 deletions music21/musicxml/m21ToXml.py
Original file line number Diff line number Diff line change
Expand Up @@ -5473,11 +5473,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 @@ -5489,7 +5496,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 @@ -5523,7 +5530,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 @@ -5539,6 +5547,63 @@ 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(bendAlter=interval.Interval(2))

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

>>> MEXclass.setbend(mxh, a)
>>> MEXclass.dump(mxh)
<bend>
<bend-alter>2</bend-alter>
</bend>
>>> a.pre-bend = True
>>> MEXclass.setbend(mxh, a)
>>> MEXclass.dump(mxh)
<bend>
<bend-alter>2</bend-alter>
<pre-bend/>
</bend>
>>> a = articulations.FretBend(bendAlter=interval.Interval(-2), release=OffsetQL(1, 10080))
>>> MEXclass.setbend(mxh, a)
>>> MEXclass.dump(mxh)
<bend>
<bend-alter>-2</bend-alter>
<release offset='1'>
</bend>
>>> a = articulations.FretBend(bendAlter=interval.Interval(-2), withBar='scoop')
>>> MEXclass.setbend(mxh, a)
>>> MEXclass.dump(mxh)
<bend>
<bend-alter>1</bend-alter>
<with-bar>scoop</with-bar>
</bend>
'''
bendAlterSubElement = SubElement(mxh, 'bend-alter')
alter = bend.bendAlter
if alter is not None:
bendAlterSubElement.text = str(alter.semitones)
if bend.preBend:
SubElement(mxh, 'pre-bend')
if bend.release is not None:
releaseSubElement = SubElement(mxh, 'release')
quarterLengthValue = bend.release
divisionsValue = int(defaults.divisionsPerQuarter * quarterLengthValue)
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
3 changes: 1 addition & 2 deletions music21/musicxml/xmlObjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@
# 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)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, I might have had issues with merging new changes and the commented lines. Fixed with 91e9f84

('tap', articulations.FretTap),
('fret', articulations.FretIndication),
('heel', articulations.OrganHeel),
Expand Down
64 changes: 62 additions & 2 deletions music21/musicxml/xmlToM21.py
Original file line number Diff line number Diff line change
Expand Up @@ -3866,17 +3866,77 @@ 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

def setBend(self, mxh, bend):
'''
Gets the bend amplitude from the bend-alter tag,
then optional pre-bend and with-bar tags are processed,
as well as release which is converted from divisions to music21 time.

Called from xmlTechnicalToArticulation

>>> from xml.etree.ElementTree import fromstring as EL
>>> MP = musicxml.xmlToM21.MeasureParser()

>>> mxTech = EL('<bend><bend-alter>2</bend-alter></bend>')
>>> a = MP.xmlTechnicalToArticulation(mxTech)
>>> a
<music21.articulations.FretBend 0>
>>> a.bendAlter.semitones
2
>>> a.release

>>> a.withBar

>>> a.preBend
False

>>> mxTech = EL('<bend><bend-alter>-2</bend-alter><pre-bend/></bend>')
>>> a = MP.xmlTechnicalToArticulation(mxTech)
>>> a.bendAlter.semitones
-2
>>> a.preBend
True

>>> mxTech = EL('<bend><bend-alter>-2</bend-alter><release offset="1"/></bend>')
>>> a = MP.xmlTechnicalToArticulation(mxTech)
>>> a.bendAlter.semitones
-2
>>> a.release
Fraction(1, 10080)

>>> mxTech = EL('<bend><bend-alter>-1</bend-alter><with-bar>dip</with-bar></bend>')
>>> a = MP.xmlTechnicalToArticulation(mxTech)
>>> a.bendAlter.semitones
-1
>>> a.withBar
'dip'
'''
alter = mxh.find('bend-alter')
if alter is not None:
if alter.text is not None:
bend.bendAlter = interval.Interval(float(alter.text))
if mxh.find('pre-bend') is not None:
bend.preBend = True
if mxh.find('with-bar') is not None:
bend.withBar = mxh.find('with-bar').text
if mxh.find('release') is not None:
try:
divisions = float(mxh.find('release').get('offset'))
bend.release = opFrac(divisions / self.divisions)
except (ValueError, TypeError) as unused_err:
bend.release = 0.0

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