from typing import Optional, Union
import abjad
[docs]def remove_repeated_dynamics(selection: abjad.Selection,
*,
ignore_hairpins: bool = False,
reset_after_rests: bool = False,
reset_after_duration: Optional[
Union[float, int, str, tuple, abjad.Duration]
] = None,
) -> None:
r"""Mutates an input |abjad.Selection| in place and has no return value;
this function removes all consecutive repeated dynamic markings.
Basic usage:
When two consecutive leaves have identical dynamics, the second
one is removed:
>>> staff = abjad.Staff(r"\time 3/8 c'4\pp d'8\pp | c'4\f d'8\f")
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/8
c'4
\pp
d'8
\pp
c'4
\f
d'8
\f
}
.. figure:: ../_images/remove_repeated_dynamics-anw32e9i0f.png
>>> auxjad.mutate.remove_repeated_dynamics(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/8
c'4
\pp
d'8
c'4
\f
d'8
}
.. figure:: ../_images/remove_repeated_dynamics-i5ylxkzv7md.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.remove_repeated_dynamics(staff[:])
>>> abjad.mutate.remove_repeated_dynamics(staff[:])
Dynamic structure:
The function also removes dynamics that are separated by an arbitrary
number of leaves without dynamics:
>>> staff = abjad.Staff(r"\time 3/8 c'4\p d'8 | e'4.\p | c'4\p d'8\f")
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/8
c'4
\p
d'8
e'4.
\p
c'4
\p
d'8
\f
}
.. figure:: ../_images/remove_repeated_dynamics-ha1x7s8d2fb.png
>>> auxjad.mutate.remove_repeated_dynamics(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/8
c'4
\p
d'8
e'4.
c'4
d'8
\f
}
.. figure:: ../_images/remove_repeated_dynamics-g157jbbojhv.png
Subcontainers:
The container from which the selection is made can also have
subcontainers:
>>> staff = abjad.Staff([abjad.Note("c'2"),
... abjad.Chord("<d' f'>2"),
... abjad.Tuplet((2, 3), "g2 a2 b2"),
... ])
>>> abjad.attach(abjad.Dynamic('ppp'), staff[0])
>>> abjad.attach(abjad.Dynamic('ppp'), staff[1])
>>> abjad.attach(abjad.Dynamic('ppp'), staff[2][0])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'2
\ppp
<d' f'>2
\ppp
\times 2/3
{
g2
\ppp
a2
b2
}
}
.. figure:: ../_images/remove_repeated_dynamics-4h9xze4780d.png
>>> auxjad.mutate.remove_repeated_dynamics(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'2
\ppp
<d' f'>2
\times 2/3
{
g2
a2
b2
}
}
.. figure:: ../_images/remove_repeated_dynamics-7n9aaveoslu.png
``ignore_hairpins``:
By default, repeated dynamics with hairpins in between are not removed,
but consecutive ones will.
>>> staff = abjad.Staff(r"c'2\p\< d'2\f\> | c'2\f d'2\f | e'1\p")
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
\<
d'1
\f
\>
c'1
\f
d'1
\f
e'1
\p
}
.. figure:: ../_images/remove_repeated_dynamics-frmlobo3gis.png
>>> auxjad.mutate.remove_repeated_dynamics(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
\<
d'1
\f
\>
c'1
\f
d'1
e'1
\p
}
.. figure:: ../_images/remove_repeated_dynamics-ov05k1imubj.png
To override the previous behaviour, set ``ignore_hairpins=True`` and
hairpins will be ignored.
>>> staff = abjad.Staff(r"c'2\p\< d'2\f\> | c'2\f d'2\f | e'1\p")
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
\<
d'1
\f
\>
c'1
\f
d'1
\f
e'1
\p
}
.. figure:: ../_images/remove_repeated_dynamics-2hdkt6cyca1.png
>>> auxjad.mutate.remove_repeated_dynamics(
... staff[:],
... ignore_hairpins=True,
... )
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'1
\p
\<
d'1
\f
\>
c'1
d'1
e'1
\p
}
.. figure:: ../_images/remove_repeated_dynamics-xkaipizr2jr.png
``reset_after_rests``:
By default, rests are treated just like any other leaf and thus notes
with an identical dynamic separated by an arbitrary number of rests
will be considered as repeated and the second dynamic will be removed.
>>> staff = abjad.Staff(r"c'4\pp r2. | c'1\pp")
>>> auxjad.mutate.remove_repeated_dynamics(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'4
\pp
r2.
c'1
}
.. figure:: ../_images/remove_repeated_dynamics-wtno2t8qroh.png
To override the previous behaviour, set ``reset_after_rests`` to
``True`` and dynamics will always be restated after a rest.
>>> staff = abjad.Staff(r"c'4\pp r2. | c'1\pp")
>>> auxjad.mutate.remove_repeated_dynamics(
... staff[:],
... reset_after_rests=True,
... )
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'4
\pp
r2.
c'1
\pp
}
.. figure:: ../_images/remove_repeated_dynamics-3e6g7u0q1i1.png
``reset_after_duration``:
With ``reset_after_rests`` is set to ``True``, it is possible to
specify a minimum duration of silence required for the dynamics to be
restated using the argument ``reset_after_duration``. It takes an
|abjad.Duration| or also :obj:`str`, :obj:`tuple`, :obj:`float`, etc.
This sets the maximum length of rests before which identical dynamics
are restated. If the total length of rests falls below that value, then
repeated dynamics are removed.
In the case below, a rest of ``r2``. is shorter than a duration of
``(4, 4)``, so the repeated dynamic is removed.
>>> staff = abjad.Staff(r"c'4\pp r2. | c'1\pp")
>>> auxjad.mutate.remove_repeated_dynamics(
... staff[:],
... reset_after_rests=True,
... reset_after_duration=(4, 4),
... )
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'4
\pp
r2.
c'1
}
.. figure:: ../_images/remove_repeated_dynamics-b323xuesujc.png
But setting the duration to ``2/4`` forces the dynamic to be restated.
>>> staff = abjad.Staff(r"c'4\pp r2. | c'1\pp")
>>> auxjad.mutate.remove_repeated_dynamics(
... staff[:],
... reset_after_rests=True,
... reset_after_duration=2 / 4,
... )
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'4
\pp
r2.
c'1
\pp
}
.. figure:: ../_images/remove_repeated_dynamics-64dppx3cp99.png
``reset_after_rests``:
The function also handles measure rests with ``reset_after_rests``.
>>> staff = abjad.Staff(r"c'4\pp r2. | c'4\pp r2. | R1 | c'1\pp")
>>> auxjad.mutate.remove_repeated_dynamics(
... staff[:],
... reset_after_rests=True,
... reset_after_duration=abjad.Duration(4, 4),
... )
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'4
\pp
r2.
c'4
r2.
R1
c'1
\pp
}
.. figure:: ../_images/remove_repeated_dynamics-jt7akhtbsge.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(ignore_hairpins, bool):
raise TypeError("'ignore_hairpins' must be 'bool'")
if not isinstance(reset_after_rests, bool):
raise TypeError("'reset_after_rests' must be 'bool'")
if reset_after_duration is not None:
if not isinstance(reset_after_duration, (abjad.Duration,
str,
tuple,
int,
float,
)):
raise TypeError("'reset_after_duration' must be 'abjad.Duration', "
"'str', 'tuple', or a number")
if not isinstance(reset_after_duration, abjad.Duration):
reset_after_duration = abjad.Duration(reset_after_duration)
previous_dynamic = None
current_dynamic = None
duration_since_last_note = abjad.Duration(0)
for leaf in selection.leaves():
if isinstance(leaf, (abjad.Rest, abjad.MultimeasureRest)):
dynamic = abjad.get.indicator(leaf, abjad.Dynamic)
if dynamic is not None:
previous_dynamic = dynamic
if reset_after_rests:
if reset_after_duration is None:
previous_dynamic = None
else:
duration_since_last_note += leaf.written_duration
if duration_since_last_note >= reset_after_duration:
previous_dynamic = None
else:
duration_since_last_note = abjad.Duration(0)
indicators = abjad.get.indicators(leaf)
hairpin_present = any([isinstance(indicator, abjad.StartHairpin)
for indicator in indicators])
if hairpin_present and not ignore_hairpins:
for indicator in indicators:
if isinstance(indicator, abjad.Dynamic):
if (indicator is not None
and indicator == previous_dynamic):
abjad.detach(abjad.Dynamic, leaf)
current_dynamic = None
else:
for indicator in indicators:
if isinstance(indicator, abjad.Dynamic):
current_dynamic = indicator
if current_dynamic != previous_dynamic:
previous_dynamic = current_dynamic
elif current_dynamic is not None:
abjad.detach(abjad.Dynamic, leaf)