import abjad
from .remove_repeated_dynamics import (
remove_repeated_dynamics as remove_repeated_dynamics_,
)
[docs]def reposition_dynamics(selection: abjad.Selection,
*,
allow_hairpins_under_rests: bool = False,
check_hairpin_trends: bool = True,
remove_repeated_dynamics: bool = True,
allow_hairpin_to_rest_with_dynamic: bool = True,
) -> None:
r"""Mutates an input |abjad.Selection| in place and has no return value;
this function shifts all dynamics from rests to the next pitched leaves. It
will also adjust hairpins if necessary.
Basic usage:
This function will shift dynamics under rests to the next pitched leaf.
>>> staff = abjad.Staff(r"c'1\p d'2 r2\f r1 e'1")
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
d'2
r2
\f
r1
e'1
}
.. figure:: ../_images/reposition_dynamics-sqwvevg7o9.png
>>> staff = abjad.Staff(r"c'1\p d'2 r2\f r1 e'1")
>>> auxjad.mutate.reposition_dynamics(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
d'2
r2
r1
e'1
\f
}
.. figure:: ../_images/reposition_dynamics-v18uzh1zjs.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_dynamics(staff[:])
>>> abjad.mutate.reposition_dynamics(staff[:])
Removing dynamics:
If the next pitched leaf already contain a dynamic, this function will
simply remove the dynamic under the rest.
>>> staff = abjad.Staff(r"c'1\p d'2 r2\f r1\mf e'1\pp")
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
d'2
r2
\f
r1
\mf
e'1
\pp
}
.. figure:: ../_images/reposition_dynamics-aom2qywcn9m.png
>>> staff = abjad.Staff(r"c'1\p d'2 r2\f r1\mf e'1\pp")
>>> auxjad.mutate.reposition_dynamics(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
d'2
r2
r1
e'1
\pp
}
.. figure:: ../_images/reposition_dynamics-2ua73x102fp.png
``remove_repeated_dynamics``:
By default indentical repeated dynamics are omitted.
>>> staff = abjad.Staff(r"c'1\p d'1 r1\f e'1\p")
>>> auxjad.mutate.reposition_dynamics(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
d'1
r1
e'1
}
.. figure:: ../_images/reposition_dynamics-i4x8b1z1ak.png
Set the optional keyword argument ``remove_repeated_dynamics`` to
``False`` to disable this behaviour.
>>> staff = abjad.Staff(r"c'1\p d'1 r1\f e'1\p")
>>> auxjad.mutate.reposition_dynamics(
... staff[:],
... remove_repeated_dynamics=False,
... )
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
d'1
r1
e'1
\p
}
.. figure:: ../_images/reposition_dynamics-se8a5aeqer.png
``allow_hairpins_under_rests``:
This function will shorten hairpins until rests by default.
>>> staff = abjad.Staff(r"c'1\p\< d'2 r2 r1\f e'1")
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
\<
d'2
r2
r1
\f
e'1
}
.. figure:: ../_images/reposition_dynamics-r28po3j3hd.png
>>> staff = abjad.Staff(r"c'1\p\< d'2 r2 r1\f e'1")
>>> auxjad.mutate.reposition_dynamics(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
\<
d'2
r2
\!
r1
e'1
\f
}
.. figure:: ../_images/reposition_dynamics-n60vvnqrcnr.png
Set the optional keyword argument ``allow_hairpins_under_rests`` to
``True`` to allow hairpins to extend cross rests.
>>> staff = abjad.Staff(r"c'1\p\< d'2 r2 r1\f e'1")
>>> auxjad.mutate.reposition_dynamics(
... staff[:],
... allow_hairpins_under_rests=True,
... )
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
\<
d'2
r2
r1
e'1
\f
}
.. figure:: ../_images/reposition_dynamics-ugit7ijz89h.png
``allow_hairpin_to_rest_with_dynamic``:
Notice that if a hairpin leads to a rest with dynamic, that one is not
removed.
>>> staff = abjad.Staff(r"c'1\p\< d'2 r2\f r1 e'1")
>>> auxjad.mutate.reposition_dynamics(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
\<
d'2
r2
\f
r1
e'1
}
.. figure:: ../_images/reposition_dynamics-t9z4y5zzj6.png
Set the argument ``allow_hairpin_to_rest_with_dynamic`` to ``False`` to
disable this behaviour.
>>> staff = abjad.Staff(r"c'1\p\< d'2 r2\f r1 e'1")
>>> auxjad.mutate.reposition_dynamics(
... staff[:],
... allow_hairpin_to_rest_with_dynamic=False,
... )
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
\<
d'2
r2
\!
r1
e'1
\f
}
.. figure:: ../_images/reposition_dynamics-wpwweov55qf.png
``check_hairpin_trends``:
This function will remove any hairpins connecting dynamics that grow in
the opposite direction to the hairpin's trend, such as a diminuendo
hairpin from piano to forte.
>>> staff = abjad.Staff(r"c'1\p\> d'1\f\> e'1\p")
>>> auxjad.mutate.reposition_dynamics(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
d'1
\f
\>
e'1
\p
}
.. figure:: ../_images/reposition_dynamics-f0b4ppb71ii.png
This behaviour can be disabled by setting the argument
``check_hairpin_trends`` to ``False``.
>>> staff = abjad.Staff(r"c'1\p\> d'1\f\> e'1\p")
>>> auxjad.mutate.reposition_dynamics(
... staff[:],
... check_hairpin_trends=False,
... )
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
\>
d'1
\f
\>
e'1
\p
}
.. figure:: ../_images/reposition_dynamics-1e2ugszm95fi.png
.. note::
The behaviour described above is only applicable when a hairpin ends
on a dynamic. Using the hairpin terminator ``\!`` before a dynamic
change will not cause a hairpin to be removed as it is not considered
to be connecting dynamics of the opposite trend.
>>> staff = abjad.Staff(r"c'1\p\> d'1\! e'1\f\> f'1\p")
>>> auxjad.mutate.reposition_dynamics(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
\>
d'1
\!
e'1
\f
\>
f'1
\p
}
.. figure:: ../_images/reposition_dynamics-77g0uwthbgd.png
Types of hairpins:
This function can handle multiple types of hairpins as well as niente
dynamics.
>>> staff = abjad.Staff(r"c'1 d'1 e'1 r1\mf r1\ff f'1 r1 g'1")
>>> abjad.attach(abjad.Dynamic('niente', hide=True), staff[0])
>>> abjad.attach(abjad.Dynamic('niente', hide=True), staff[7])
>>> abjad.attach(abjad.StartHairpin('o<'), staff[0])
>>> abjad.attach(abjad.StartHairpin('>o'), staff[4])
>>> abjad.attach(abjad.StopHairpin(), staff[7])
>>> auxjad.mutate.reposition_dynamics(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
- \tweak circled-tip ##t
\<
d'1
e'1
r1
\mf
r1
f'1
\ff
- \tweak circled-tip ##t
\>
r1
\!
g'1
}
.. figure:: ../_images/reposition_dynamics-m8it9awv1ce.png
>>> staff = abjad.Staff(
... r"c'1\p d'1\f\> e'1\ff\< r1\fff f'1\p\> g'1\ppp"
... )
>>> abjad.attach(abjad.StartHairpin('--'), staff[0])
>>> auxjad.mutate.reposition_dynamics(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
- \tweak stencil #constante-hairpin
\<
d'1
\f
e'1
\ff
\<
r1
\fff
f'1
\p
\>
g'1
\ppp
}
.. figure:: ../_images/reposition_dynamics-fosad5ltzj.png
Multi-measure rests:
Multi-measure rests are also supported.
>>> staff = abjad.Staff(r"c'1\p R1\f d'1")
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
R1
\f
d'1
}
.. figure:: ../_images/reposition_dynamics-uj6jasfs2uh.png
>>> staff = abjad.Staff(r"c'1\p R1\f d'1")
>>> auxjad.mutate.reposition_dynamics(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
R1
d'1
\f
}
.. figure:: ../_images/reposition_dynamics-axpcbm9hocd.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.Selection'")
if not abjad.select(selection).leaves().are_contiguous_logical_voice():
raise ValueError("argument must be contiguous logical voice")
if not isinstance(allow_hairpins_under_rests, bool):
raise TypeError("'allow_hairpins_under_rests' must be 'bool'")
if not isinstance(check_hairpin_trends, bool):
raise TypeError("'check_hairpin_trends' must be 'bool'")
if not isinstance(remove_repeated_dynamics, bool):
raise TypeError("'remove_repeated_dynamics' must be 'bool'")
if not isinstance(allow_hairpin_to_rest_with_dynamic, bool):
raise TypeError("'allow_hairpin_to_rest_with_dynamic' must be 'bool'")
leaves = selection.leaves()
# shifting dynamics and hairpins from rests to notes
shifted_dynamic = None
shifted_hairpin = None
active_hairpin = None
for leaf in leaves:
if isinstance(leaf, (abjad.Rest, abjad.MultimeasureRest)):
if abjad.get.indicator(leaf, abjad.Dynamic) is not None:
previous_leaf = abjad.select(leaf).with_previous_leaf()[0]
if (allow_hairpin_to_rest_with_dynamic
and active_hairpin is not None
and not isinstance(
previous_leaf,
(abjad.Rest, abjad.MultimeasureRest),
)):
active_hairpin = None
else:
shifted_dynamic = abjad.get.indicator(leaf, abjad.Dynamic)
abjad.detach(abjad.Dynamic, leaf)
if abjad.get.indicator(leaf, abjad.StartHairpin) is not None:
shifted_hairpin = abjad.get.indicator(leaf, abjad.StartHairpin)
abjad.detach(abjad.StartHairpin, leaf)
else:
if abjad.get.indicator(leaf, abjad.Dynamic) is None:
if shifted_dynamic is not None:
abjad.attach(shifted_dynamic, leaf)
if (abjad.get.indicator(leaf, abjad.StopHairpin)
is not None):
abjad.detach(abjad.StopHairpin, leaf)
if shifted_hairpin is not None:
abjad.attach(shifted_hairpin, leaf)
else:
active_hairpin = None
if abjad.get.indicator(leaf, abjad.StopHairpin) is not None:
active_hairpin = None
shifted_dynamic = None
shifted_hairpin = None
if active_hairpin is None:
active_hairpin = abjad.get.indicator(leaf, abjad.StartHairpin)
# stopping hairpins under rests if not allowed
if not allow_hairpins_under_rests:
effective_hairpin = None
for leaf in leaves:
start_hairpin = abjad.get.indicator(leaf, abjad.StartHairpin)
if start_hairpin is not None:
effective_hairpin = start_hairpin
continue
if isinstance(leaf, (abjad.Rest, abjad.MultimeasureRest)):
if effective_hairpin is not None:
if abjad.get.indicator(leaf, abjad.StopHairpin) is None:
abjad.attach(abjad.StopHairpin(), leaf)
effective_hairpin = None
else:
dynamic = abjad.get.indicator(leaf, abjad.Dynamic)
stop_hairpin = abjad.get.indicator(leaf, abjad.StopHairpin)
if dynamic is not None or stop_hairpin is not None:
effective_hairpin = None
# cleaning up hairpins
effective_dynamic = None
for index, leaf in enumerate(leaves[:-1]):
if abjad.get.indicator(leaf, abjad.Dynamic) is not None:
effective_dynamic = abjad.get.indicator(leaf, abjad.Dynamic)
start_hairpin = abjad.get.indicator(leaf, abjad.StartHairpin)
if start_hairpin is not None and check_hairpin_trends:
for next_leaf in leaves[index + 1:]:
next_dynamic = abjad.get.indicator(next_leaf, abjad.Dynamic)
if next_dynamic is not None and effective_dynamic is not None:
if '<' in start_hairpin.shape:
if next_dynamic.ordinal <= effective_dynamic.ordinal:
abjad.detach(abjad.StartHairpin, leaf)
elif '>' in start_hairpin.shape:
if next_dynamic.ordinal >= effective_dynamic.ordinal:
abjad.detach(abjad.StartHairpin, leaf)
break
elif (abjad.get.indicator(next_leaf, abjad.StopHairpin)
is not None):
break
if abjad.get.indicator(leaves[-1], abjad.StartHairpin) is not None:
abjad.detach(abjad.StartHairpin, leaves[-1])
# removing unecessary StopHairpin's
for leaf in leaves:
if (abjad.get.indicator(leaf, abjad.StopHairpin) is not None
and abjad.get.indicator(leaf, abjad.Dynamic) is not None):
abjad.detach(abjad.StopHairpin, leaf)
target_leaf = None
for leaf in leaves[::-1]:
if abjad.get.indicator(leaf, abjad.StopHairpin) is not None:
if target_leaf is not None:
abjad.detach(abjad.StopHairpin, target_leaf)
target_leaf = leaf
elif (abjad.get.indicator(leaf, abjad.StartHairpin) is not None
or abjad.get.indicator(leaf, abjad.Dynamic)) is not None:
target_leaf = None
# removing repeated dynamics if required
if remove_repeated_dynamics:
remove_repeated_dynamics_(selection)