import abjad
[docs]def reposition_slurs(selection: abjad.Selection,
*,
allow_slurs_under_rests: bool = False,
close_unterminated_final_slur: bool = True,
) -> None:
r"""Mutates an input |abjad.Selection| in place and has no return value;
this function repositions all slurs that starts or ends on rests.
Basic usage:
This function will shift slurs that ends on rests to the previous
pitched leaf.
>>> staff = abjad.Staff(r"c'1( d'2 r2) r1 e'1")
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
(
d'2
r2
)
r1
e'1
}
.. figure:: ../_images/reposition_slurs-uxji4xx6ftk.png
>>> staff = abjad.Staff(r"c'1( d'2 r2) r1 e'1")
>>> auxjad.mutate.reposition_slurs(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
(
d'2
)
r2
r1
e'1
}
.. figure:: ../_images/reposition_slurs-7nnp5cttm4y.png
.. note::
Auxjad automatically adds this function as an extension function to
|abjad.mutate|. It can thus be used from either |auxjad.mutate|_ or
|abjad.mutate| namespaces. Therefore, the two lines below are
equivalent:
>>> auxjad.mutate.reposition_slurs(staff[:])
>>> abjad.mutate.reposition_slurs(staff[:])
Rests:
Slurs starting on rests are shifted to the next pitched leaf.
>>> staff = abjad.Staff(r"c'1 r2( d'2 e'1)")
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
r2
(
d'2
e'1
)
}
.. figure:: ../_images/reposition_slurs-2j7hgqd7bt1.png
>>> staff = abjad.Staff(r"c'1 r2( d'2 e'1)")
>>> auxjad.mutate.reposition_slurs(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
r2
d'2
(
e'1
)
}
.. figure:: ../_images/reposition_slurs-i957u1wt30m.png
Multiple rests:
This function also works when multiple rests are present.
>>> staff = abjad.Staff(r"c'1( d'2 r2 r1) e'1")
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
(
d'2
r2
r1
)
e'1
}
.. figure:: ../_images/reposition_slurs-v76u42x7idk.png
>>> staff = abjad.Staff(r"c'1( d'2 r2 r1) e'1")
>>> auxjad.mutate.reposition_slurs(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
(
d'2
)
r2
r1
e'1
}
.. figure:: ../_images/reposition_slurs-burs1t0daid.png
``allow_slurs_under_rests``:
By default, a slur crossing a rest is broken into two.
>>> staff = abjad.Staff(r"c'1( d'2 r2 e'1 f'1)")
>>> auxjad.mutate.reposition_slurs(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
(
d'2
)
r2
e'1
(
f'1
)
}
.. figure:: ../_images/reposition_slurs-8wb7orpt285.png
Set the optional keyword argument ``allow_slurs_under_rests`` to
``True`` to allow slurs under rests.
>>> staff = abjad.Staff(r"c'1( d'2 r2 e'1 f'1)")
>>> auxjad.mutate.reposition_slurs(
... staff[:],
... allow_slurs_under_rests=True,
... )
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
(
d'2
r2
e'1
f'1
)
}
.. figure:: ../_images/reposition_slurs-ftb59kz6u8j.png
``close_unterminated_final_slur``:
By default, unterminated slurs at the end of the selection are closed
when possible or removed when not.
>>> staff = abjad.Staff(r"c'1( d'2 r2 e'2 f'2) g'1( a'1")
>>> auxjad.mutate.reposition_slurs(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
(
d'2
)
r2
e'2
(
f'2
)
g'1
(
a'1
)
}
.. figure:: ../_images/reposition_slurs-70dli8e0kqr.png
Set the optional keyword argument ``close_unterminated_final_slur`` to
``False`` to disable this behaviour.
>>> staff = abjad.Staff(r"c'1( d'2 r2 e'2 f'2) g'1( a'1")
>>> auxjad.mutate.reposition_slurs(
... staff[:],
... close_unterminated_final_slur=False,
... )
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
(
d'2
)
r2
e'2
(
f'2
)
g'1
(
a'1
}
.. figure:: ../_images/reposition_slurs-1usa2dezl45.png
When there are no pitched leaves left after an unterminated open slur,
it is removed.
>>> staff = abjad.Staff(r"c'1( d'2 r2 e'2 f'2) g'1( r1")
>>> auxjad.mutate.reposition_slurs(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
(
d'2
)
r2
e'2
(
f'2
)
g'1
r1
}
.. figure:: ../_images/reposition_slurs-a0uakfcltuf.png
.. note::
Duplicate slur starts or stops are removed. Note that the score output
will not change, as LilyPond also ignores duplicate slurs, but the
output in the ``.ly`` file will be cleaner.
>>> staff = abjad.Staff(r"c'1( d'2) e'2) f'2( g'2( a'1)")
>>> auxjad.mutate.reposition_slurs(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
(
d'2
)
e'2
f'2
(
g'2
a'1
)
}
.. figure:: ../_images/reposition_slurs-0ugn322x3tr.png
.. warning::
The input selection must be a contiguous logical voice. When dealing
with a container with multiple subcontainers (e.g. a score containing
multiple staves), the best approach is to cycle through these
subcontainers, applying this function to them individually.
"""
if not isinstance(selection, abjad.Selection):
raise TypeError("argument must be 'abjad.Container' or child class")
if not selection.leaves().are_contiguous_logical_voice():
raise ValueError("argument must be contiguous logical voice")
if not isinstance(allow_slurs_under_rests, bool):
raise TypeError("'allow_slurs_under_rests' must be 'bool'")
if not isinstance(close_unterminated_final_slur, bool):
raise TypeError("'close_unterminated_final_slur' must be 'bool'")
leaves = selection.leaves()
# checking for final unfinished slurs
if close_unterminated_final_slur:
for leaf in leaves[::-1]:
if abjad.get.indicator(leaf, abjad.StartSlur) is not None:
if leaf is leaves[-1]:
abjad.detach(abjad.StartSlur, leaf)
elif (abjad.get.indicator(leaves[-1], abjad.StopSlur) is None):
abjad.attach(abjad.StopSlur(), leaves[-1])
if abjad.get.indicator(leaf, abjad.StopSlur) is not None:
break
# checking for duplicate open or close slurs
start_slur_count = 0
stop_slur_count = 0
for leaf in leaves:
if abjad.get.indicator(leaf, abjad.StartSlur) is not None:
start_slur_count += 1
stop_slur_count = 0
if start_slur_count > 1:
abjad.detach(abjad.StartSlur, leaf)
elif abjad.get.indicator(leaf, abjad.StopSlur) is not None:
stop_slur_count += 1
start_slur_count = 0
if stop_slur_count > 1:
abjad.detach(abjad.StopSlur, leaf)
# shifting slurs from rests to notes
shifted_startslur = None
for leaf in leaves:
if isinstance(leaf, (abjad.Rest, abjad.MultimeasureRest)):
if abjad.get.indicator(leaf, abjad.StartSlur) is not None:
shifted_startslur = abjad.get.indicator(leaf, abjad.StartSlur)
abjad.detach(abjad.StartSlur, leaf)
else:
if abjad.get.indicator(leaf, abjad.StartSlur) is None:
if shifted_startslur is not None:
abjad.attach(shifted_startslur, leaf)
shifted_startslur = None
shifted_stopslur = None
for leaf in leaves[::-1]:
if isinstance(leaf, (abjad.Rest, abjad.MultimeasureRest)):
if abjad.get.indicator(leaf, abjad.StopSlur) is not None:
shifted_stopslur = abjad.get.indicator(leaf, abjad.StopSlur)
abjad.detach(abjad.StopSlur, leaf)
else:
if abjad.get.indicator(leaf, abjad.StopSlur) is None:
if shifted_stopslur is not None:
abjad.attach(shifted_stopslur, leaf)
shifted_stopslur = None
# splitting slurs under rests
if not allow_slurs_under_rests:
active_slur = False
for index, leaf in enumerate(leaves):
if abjad.get.indicator(leaf, abjad.StartSlur) is not None:
active_slur = True
elif abjad.get.indicator(leaf, abjad.StopSlur) is not None:
if not active_slur:
abjad.detach(abjad.StopSlur, leaf)
active_slur = False
if (isinstance(leaf, (abjad.Rest, abjad.MultimeasureRest))
and active_slur):
previous_leaf = abjad.select(leaf).with_previous_leaf()[0]
if (abjad.get.indicator(previous_leaf, abjad.StopSlur)
is None):
abjad.attach(abjad.StopSlur(), previous_leaf)
active_slur = False
for next_leaf in leaves[index + 1:]:
if not isinstance(next_leaf, (abjad.Rest,
abjad.MultimeasureRest,
)):
if (abjad.get.indicator(next_leaf, abjad.StartSlur)
is None):
abjad.attach(abjad.StartSlur(), next_leaf)
break
for leaf in leaves:
if (abjad.get.indicator(leaf, abjad.StartSlur) is not None
and abjad.get.indicator(leaf, abjad.StopSlur) is not None):
abjad.detach(abjad.StartSlur, leaf)
abjad.detach(abjad.StopSlur, leaf)
# removing slurs spanning a single logical tie
for logical_tie in selection.logical_ties():
if (abjad.get.indicator(logical_tie[0], abjad.StartSlur) is not None
and abjad.get.indicator(logical_tie[-1], abjad.StopSlur)
is not None):
abjad.detach(abjad.StartSlur, logical_tie[0])
abjad.detach(abjad.StopSlur, logical_tie[-1])