import random
from typing import Optional, Union
import abjad
from .. import get, mutate
[docs]class Echoer():
r"""Takes an |abjad.Container| (or child class) as input and, using it as
reference, gradually lowers all dynamics, removing notes that are below a
given threshold, returning the output as an |abjad.Selection|.
Basic usage:
Calling the object will return an |abjad.Selection| generated by the
echoing process. Each call of the object will apply the echoing process
to the previous result. By default, the container will be faded out
(that is, its notes will be gradually removed one by one). Note that,
by default, the first call in fade out mode outputs the initial
container, with subsequent calls replacing leaves for rests.
>>> container = abjad.Container(r"c'4\mf d'4\mp e'\p f'\pp")
>>> abjad.show(container)
.. docs::
{
c'4
\mf
d'4
\mp
e'4
\p
f'4
\pp
}
.. figure:: ../_images/Echoer-jVGtGRdQev.png
>>> echoer = auxjad.Echoer(container)
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\mf
d'4
\mp
e'4
\p
f'4
\pp
}
.. figure:: ../_images/Echoer-4e4pnLJzwI.png
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\mp
d'4
\p
e'4
\pp
f'4
\ppp
}
.. figure:: ../_images/Echoer-4bebflmnff9.png
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\p
d'4
\pp
e'4
\ppp
r4
}
.. figure:: ../_images/Echoer-64h0bb02goc.png
The property :attr:`current_window` can be used to access the current
window without processing it.
>>> notes = echoer.current_window()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\p
d'4
\pp
e'4
\ppp
r4
}
.. figure:: ../_images/Echoer-ruetb1tzhtn.png
:attr:`process_on_first_call`:
The very first call will output the input container without processing
it. To disable this behaviour and apply the echoing process on the very
first call, initialise the class with the keyword argument
:attr:`process_on_first_call` set to ``True``.
>>> container = abjad.Container(r"c'4\mf d'4\mp e'\p f'\pp")
>>> echoer = auxjad.Echoer(container,
... process_on_first_call=True,
... )
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\mp
d'4
\p
e'4
\pp
f'4
\ppp
}
.. figure:: ../_images/Echoer-rbnsf64kjoq.png
:attr:`min_dynamic`:
The threshold dynamic used to remove softer notes during each iteration
of the process is set by :attr:`min_dynamic`.
>>> container = abjad.Container(r"c'4\f d'4 e'4 f'4")
>>> echoer = auxjad.Echoer(container,
... min_dynamic='mp',
... )
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\mf
d'4
\mp
e'4
\p
f'4
\pp
}
.. figure:: ../_images/Echoer-dzjqv7lsdis.png
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\mp
d'4
\p
r2
}
.. figure:: ../_images/Echoer-f33perr6lfo.png
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\p
r2.
}
.. figure:: ../_images/Echoer-qxvj8lkfph.png
Changing :attr:`min_dynamic` after initialisation:
The property :attr:`min_dynamic` can also be changed after
initialisation, as shown below.
>>> container = abjad.Container(r"c'4\mf d'4\mp e'\p f'\pp")
>>> echoer = auxjad.Echoer(container)
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\mf
d'4
\mp
e'4
\p
f'4
\pp
}
.. figure:: ../_images/Echoer-3ldjnprohuo.png
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\mp
d'4
\p
e'4
\pp
f'4
\ppp
}
.. figure:: ../_images/Echoer-wzeaqjgouz8.png
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\p
d'4
\pp
e'4
\ppp
r4
}
.. figure:: ../_images/Echoer-aqq1docvezb.png
>>> echoer.min_dynamic = 'pp'
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\pp
r2.
}
.. figure:: ../_images/Echoer-3jt6kto85h1.png
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
R1
}
.. figure:: ../_images/Echoer-2i22so5t5pf.png
:attr:`include_empty_measures`:
Set :attr:`include_empty_measures` to ``False`` to exclude empty
measures at the end of the process (default is ``True``).
>>> container = abjad.Container(r"c'4\p d'4 e' f'")
>>> echoer = auxjad.Echoer(container,
... include_empty_measures=False,
... )
>>> staff = abjad.Staff(echoer.output_all())
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\p
d'4
e'4
f'4
c'4
\pp
d'4
e'4
f'4
c'4
\ppp
d'4
e'4
f'4
R1
}
.. figure:: ../_images/Echoer-pg0bejhb7ke.png
Using as iterator:
The instances of this class can also be used as an iterator, which can
then be used in a for loop to run through the whole process. Note that
unlike the methods :meth:`output_n` and :meth:`output_all`, time
signatures are added to each window returned by the echoer. Use the
function |auxjad.mutate.remove_repeated_time_signatures()| to clean
the output when using this class in this way.
>>> container = abjad.Container(r"c'4\mf d'4\mp e'\p f'\pp")
>>> echoer = auxjad.Echoer(container)
>>> staff = abjad.Staff()
>>> for window in echoer:
... staff.append(window)
>>> auxjad.mutate.remove_repeated_time_signatures(staff)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\mf
d'4
\mp
e'4
\p
f'4
\pp
\time 4/4
c'4
\mp
d'4
\p
e'4
\pp
f'4
\ppp
\time 4/4
c'4
\p
d'4
\pp
e'4
\ppp
r4
\time 4/4
c'4
\pp
d'4
\ppp
r2
\time 4/4
c'4
\ppp
r2.
\time 4/4
R1
}
.. figure:: ../_images/Echoer-qyve2exm08p.png
Arguments and properties:
This class can take many optional keyword arguments during its
creation, besides :attr:`min_dynamic`. By default, calling the object
for the first time will return the original container; set
:attr:`process_on_first_call` to ``True`` and the echo process will be
applied on the very first call. :attr:`max_steps` sets the maximum
number of iterations of the echoing process that can be applied in a
single call, ranging between ``1`` and the input value (default is also
``1``). :attr:`repetition_chance` sets the chance of a window repeating
itself, from ``0.0`` to ``1.0`` (default is ``0.0``, i.e. no
repetitions). :attr:`disable_rewrite_meter` disables the
|abjad.Meter.rewrite_meter()| mutation which is applied to the
container after every call, and :attr:`omit_time_signatures` will
remove all time signatures from the output (both are ``False`` by
default). Any measure filled with rests will be rewritten using a
multi-measure rest; set the :attr:`use_multimeasure_rests` to ``False``
to disable this behaviour. The properties :attr:`boundary_depth`,
:attr:`maximum_dot_count`, and :attr:`rewrite_tuplets` are passed as
arguments to |abjad.Meter.rewrite_meter()|, see its documentation for
more information.
>>> container = abjad.Container(r"c'4\mf d'4\mp e'\p f'\pp")
>>> echoer = auxjad.Echoer(container,
... min_dynamic='p',
... max_steps=2,
... repetition_chance=0.7,
... disable_rewrite_meter=True,
... omit_time_signatures=True,
... use_multimeasure_rests=False,
... boundary_depth=0,
... maximum_dot_count=1,
... rewrite_tuplets=False,
... process_on_first_call=True,
... include_empty_measures=False,
... )
>>> echoer.min_dynamic
'p'
>>> echoer.max_steps
2
>>> echoer.repetition_chance
0.7
>>> echoer.disable_rewrite_meter
True
>>> echoer.omit_time_signatures
True
>>> echoer.use_multimeasure_rests
False
>>> echoer.boundary_depth
0
>>> echoer.maximum_dot_count
1
>>> echoer.rewrite_tuplets
False
>>> echoer.process_on_first_call
True
>>> echoer.include_empty_measures
False
Use the properties below to change these values after initialisation.
>>> echoer.min_dynamic = 'mp'
>>> echoer.max_steps = 1
>>> echoer.repetition_chance = 0.23
>>> echoer.disable_rewrite_meter = False
>>> echoer.omit_time_signatures = False
>>> echoer.use_multimeasure_rests = True
>>> echoer.boundary_depth = 1
>>> echoer.maximum_dot_count = 2
>>> echoer.rewrite_tuplets = True
>>> echoer.process_on_first_call = False
>>> echoer.include_empty_measures = True
>>> echoer.min_dynamic
'mp'
>>> echoer.max_steps
1
>>> echoer.repetition_chance
0.23
>>> echoer.disable_rewrite_meter
False
>>> echoer.omit_time_signatures
False
>>> echoer.use_multimeasure_rests
True
>>> echoer.boundary_depth
1
>>> echoer.maximum_dot_count
2
>>> echoer.rewrite_tuplets
True
>>> echoer.process_on_first_call
False
>>> echoer.include_empty_measures
True
:attr:`contents`:
Use the :attr:`contents` property to read as well as overwrite the
contents of the echoer. Notice that the process will also be reset at
that point.
>>> container = abjad.Container(r"c'4\mf d'4\mp e'\p f'\pp")
>>> echoer = auxjad.Echoer(container)
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\mf
d'4
\mp
e'4
\p
f'4
\pp
}
.. figure:: ../_images/Echoer-nv5f76rv7f.png
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\mp
d'4
\p
e'4
\pp
f'4
\ppp
}
.. figure:: ../_images/Echoer-6fr4wrb8god.png
>>> echoer.contents = abjad.Container(r"c'16\f d'16 e'16 f'16 g'2.\p")
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'16
\f
d'16
e'16
f'16
g'2.
\p
}
.. figure:: ../_images/Echoer-p8q5x8ti2d.png
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'16
\mf
d'16
e'16
f'16
g'2.
\pp
}
.. figure:: ../_images/Echoer-mky4pulzf4i.png
:meth:`output_all`:
To run through the whole process and output it as a single container,
use the method :meth:`output_all`.
>>> container = abjad.Container(r"c'4\mf d'4\mp e'\p f'\pp")
>>> echoer = auxjad.Echoer(container)
>>> notes = echoer.output_all()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\mf
d'4
\mp
e'4
\p
f'4
\pp
c'4
\mp
d'4
\p
e'4
\pp
f'4
\ppp
c'4
\p
d'4
\pp
e'4
\ppp
r4
c'4
\pp
d'4
\ppp
r2
c'4
r2.
R1
}
.. figure:: ../_images/Echoer-kiqwdhyx9vk.png
:meth:`output_n`:
To run through just part of the process and output it as a single
container, use the method :meth:`output_n` and pass the number of
iterations as argument.
>>> container = abjad.Container(r"c'4\mf d'4\mp e'\p f'\pp")
>>> echoer = auxjad.Echoer(container)
>>> notes = echoer.output_n(3)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\mf
d'4
\mp
e'4
\p
f'4
\pp
c'4
\mp
d'4
\p
e'4
\pp
f'4
\ppp
c'4
\p
d'4
\pp
e'4
\ppp
r4
}
.. figure:: ../_images/Echoer-6mqxj9b5f13.png
Chords and rests:
This class also support chords and rest in :attr:`contents`.
>>> container = abjad.Container(
... r"c'4\p ~ c'16 r8. r8. <d' e'>16\mp ~ <d' e'>4"
... )
>>> echoer = auxjad.Echoer(container)
>>> staff = abjad.Staff(echoer.output_all())
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\p
~
c'16
r8.
r8.
<d' e'>16
\mp
~
<d' e'>4
c'4
\pp
~
c'16
r8.
r8.
<d' e'>16
\p
~
<d' e'>4
c'4
\ppp
~
c'16
r8.
r8.
<d' e'>16
\pp
~
<d' e'>4
r2
r8.
<d' e'>16
\ppp
~
<d' e'>4
R1
}
.. figure:: ../_images/Echoer-wjphyrz750d.png
:func:`len()`:
The function :func:`len()` returns the total number of notes in
:attr:`contents`.
>>> abjad.Container(r"c'4\mf d'4\mp e'\p f'\pp")
>>> echoer = auxjad.Echoer(container)
>>> len(echoer)
4
>>> container = abjad.Container(
... r"c'4\mf ~ c'8 d'8\mp e'4\p ~ e'8 f'8\pp"
... )
>>> echoer = auxjad.Echoer(container)
>>> len(echoer)
4
>>> container = abjad.Container(
... r"c'4\mf ~ c'16 r16 d'8\mp e'4\p ~ e'8 f'16\pp r16"
... )
>>> echoer = auxjad.Echoer(container)
>>> len(echoer)
4
Note that each chord will count as a single unit.
>>> container = abjad.Container(r"<c' e' g'>2\f <d' f'>2\p")
>>> echoer = auxjad.Echoer(container)
>>> len(echoer)
2
>>> container = abjad.Container(
... r"<c' e' g'>4\f ~ <c' e' g'>16 r8. <d' f'>2\p"
... )
>>> echoer = auxjad.Echoer(container)
>>> len(echoer)
2
>>> container = abjad.Container(
... r"<c' e' g'>4\f d'4\mf <e' g' b'>4\p r4"
... )
>>> echoer = auxjad.Echoer(container)
>>> len(echoer)
3
:attr:`max_steps`:
Setting the keyword argument :attr:`max_steps` to a value larger than
``1`` will result in a random number of steps (between ``1`` and
:attr:`max_steps`) being applied at each call.
>>> container = abjad.Container(r"c'2\fff d'2\mf")
>>> echoer = auxjad.Echoer(container,
... max_steps=3,
... )
>>> notes = echoer.output_n(3)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'2
\fff
d'2
\mf
c'2
\f
d'2
\p
c'2
\mf
d'2
\pp
}
.. figure:: ../_images/Echoer-72wpb0iqtes.png
:attr:`repetition_chance`:
Use :attr:`repetition_chance` to set the chance of a measure repeating
itself, ranging from ``0.0`` to ``1.0`` (default is ``0.0``,
i.e. no repetitions).
>>> container = abjad.Container(r"c'4.\f d'8\p e'4..\mf f'16\mp")
>>> echoer = auxjad.Echoer(container,
... repetition_chance=0.5,
... )
>>> notes = echoer.output_n(5)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4.
\f
d'8
\p
e'4..
\mf
f'16
\mp
c'4.
\f
d'8
\p
e'4..
\mf
f'16
\mp
c'4.
\mf
d'8
\pp
e'4..
\mp
f'16
\p
c'4.
\mp
d'8
\ppp
e'4..
\p
f'16
\pp
c'4.
\mp
d'8
\ppp
e'4..
\p
f'16
\pp
}
.. figure:: ../_images/Echoer-1t2yh8imiu8.png
:attr:`use_multimeasure_rests` and :attr:`disable_rewrite_meter`:
By default, all rests in a measure filled only with rests will be
converted into a multi-measure rest. Set :attr:`use_multimeasure_rests`
to ``False`` to disable this. Also, by default, all output is mutated
through |abjad.Meter.rewrite_meter()|. To disable it, set
:attr:`disable_rewrite_meter` to ``True``.
>>> container = abjad.Container(r"c'4\p ~ c'16 d'8.\mp ~ d'2")
>>> echoer = auxjad.Echoer(container,
... disable_rewrite_meter=True,
... use_multimeasure_rests=False,
... )
>>> notes = echoer.output_all()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\p
~
c'16
d'8.
\mp
~
d'2
c'4
\pp
~
c'16
d'8.
\p
~
d'2
c'4
\ppp
~
c'16
d'8.
\pp
~
d'2
r4
r16
d'8.
\ppp
~
d'2
r4
r16
r8.
r2
}
.. figure:: ../_images/Echoer-lax06gkb3ap.png
:attr:`omit_time_signatures`:
To disable time signatures altogether, initialise this class with the
keyword argument :attr:`omit_time_signatures` set to ``True`` (default
is ``False``), or use the :attr:`omit_time_signatures` property after
initialisation.
>>> container = abjad.Container(
... r"\time 2/4 c'4\mf d'4 \time 3/4 e'4\p f'4 g'4"
... )
>>> echoer = auxjad.Echoer(container,
... omit_time_signatures=True,
... )
>>> notes = echoer.output_n(3)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'4
\mf
d'4
e'4
\p
f'4
g'4
c'4
\mp
d'4
e'4
\pp
f'4
g'4
c'4
\p
d'4
e'4
\ppp
f'4
g'4
}
.. figure:: ../_images/Echoer-pcq5ecwz7ff.png
.. tip::
All methods that return an |abjad.Selection| will add an initial time
signature to it. The :meth:`output_n` and :meth:`output_all` methods
automatically remove repeated time signatures. When joining selections
output by multiple method calls, use
|auxjad.mutate.remove_repeated_time_signatures()| on the whole
container after fusing the selections to remove any unecessary time
signature changes.
Tweaking |abjad.Meter.rewrite_meter()|:
This function uses the default logical tie splitting algorithm from
|abjad.Meter.rewrite_meter()|.
>>> container = abjad.Container(r"c'4.\mf d'8 e'2")
>>> echoer = auxjad.Echoer(container)
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4.
\mf
d'8
e'2
}
.. figure:: ../_images/Echoer-93hcv2prkua.png
Set :attr:`boundary_depth` to a different number to change its
behaviour.
>>> echoer = auxjad.Echoer(container,
... boundary_depth=1,
... )
>>> notes = echoer()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\mf
~
c'8
d'8
e'2
}
.. figure:: ../_images/Echoer-cq661zyctf.png
Other arguments available for tweaking the output of
|abjad.Meter.rewrite_meter()| are :attr:`maximum_dot_count` and
:attr:`rewrite_tuplets`, which work exactly as the identically named
arguments of |abjad.Meter.rewrite_meter()|.
This class also accepts the arguments ``fuse_across_groups_of_beats``,
``fuse_quadruple_meter``, ``fuse_triple_meter``, and
``extract_trivial_tuplets``, which are passed on to
|auxjad.mutate.prettify_rewrite_meter()| (the latter can be disabled
by setting ``prettify_rewrite_meter`` to ``False``). See the
documentation of this function for more details on these arguments.
Indicators:
This class can handle dynamics and articulations.
>>> container = abjad.Container(
... r"\time 3/4 c'8.->\mf d'16 ~ d'4 e'8..--\p f'32-.\f"
... )
>>> echoer = auxjad.Echoer(container)
>>> notes = echoer.output_all()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/4
c'8.
\mf
- \accent
d'16
~
d'4
e'8..
\p
- \tenuto
f'32
\f
- \staccato
c'8.
\mp
- \accent
d'16
~
d'4
e'8..
\pp
- \tenuto
f'32
\mf
- \staccato
c'8.
\p
- \accent
d'16
~
d'4
e'8..
\ppp
- \tenuto
f'32
\mp
- \staccato
c'8.
\pp
- \accent
d'16
~
d'4
r8..
f'32
\p
- \staccato
c'8.
\ppp
- \accent
d'16
~
d'4
r8..
f'32
\pp
- \staccato
r2
r8..
f'32
\ppp
- \staccato
R1 * 3/4
}
.. figure:: ../_images/Echoer-ox08wd3ljps.png
Slurs and hairpins:
Slurs and hairpins are also supported. Slurs are split when rests
appear in the middle of a slurred phrase, while hairpins are shortened
and adjusted as required.
>>> container = abjad.Container(
... r"\times 2/3 {c'2(\p\< d'2 e'2\ff} f'4\mf\> g'2 a'4\mp)"
... )
>>> echoer = auxjad.Echoer(container)
>>> notes = echoer.output_n(5)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\times 2/3
{
\time 4/4
c'2
\p
\<
(
d'2
e'2
\ff
}
f'4
\mf
\>
g'2
a'4
\mp
)
\times 2/3
{
c'2
\pp
\<
(
d'2
e'2
\f
}
f'4
\mp
\>
g'2
a'4
\p
)
\times 2/3
{
c'2
\ppp
\<
(
d'2
e'2
\mf
}
f'4
\p
\>
g'2
a'4
\pp
)
\times 2/3
{
r1
e'2
\mp
(
}
f'4
\pp
\>
g'2
a'4
\ppp
)
\times 2/3
{
r1
e'2
\p
(
}
f'4
\ppp
\>
g'2
)
r4
\!
}
.. figure:: ../_images/Echoer-jprjps4zxej.png
.. tip::
The functions |auxjad.mutate.remove_repeated_dynamics()| and
|auxjad.mutate.reposition_clefs()| can be used to clean the output
and remove repeated dynamics and unnecessary clef changes.
.. warning::
Do note that some elements that span multiple notes (such as ottava
indicators, manual beams, etc.) can become problematic when notes
containing them are split into two. As a rule of thumb, it is always
better to attach those to the music after the echoing process has
ended.
Tuplets:
This class can handle tuplets.
>>> container = abjad.Container(
... r"\times 2/3 {c'8\ppp d'8\mp e'8} d'2.\pp"
... )
>>> echoer = auxjad.Echoer(container)
>>> notes = echoer.output_all()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\times 2/3
{
\time 4/4
c'8
\ppp
d'8
\mp
e'8
}
d'2.
\pp
\times 2/3
{
r8
d'8
\p
e'8
}
d'2.
\ppp
\times 2/3
{
r8
d'8
\pp
e'8
}
r2.
\times 2/3
{
r8
d'8
\ppp
e'8
}
r2.
R1
}
.. figure:: ../_images/Echoer-888tqk73kw3.png
Time signature changes:
This class can handle time signature changes.
>>> container = abjad.Container(
... r"\time 3/8 c'4.\pp \time 2/4 d'2\ff \time 3/8 e'4.\mp"
... )
>>> echoer = auxjad.Echoer(container)
>>> notes = echoer.output_n()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/8
c'4.
\pp
\time 2/4
d'2
\ff
\time 3/8
e'4.
\mp
c'4.
\ppp
\time 2/4
d'2
\f
\time 3/8
e'4.
\p
R1 * 3/8
\time 2/4
d'2
\mf
\time 3/8
e'4.
\pp
}
.. figure:: ../_images/Echoer-lkhKFVuUgx.png
"""
### CLASS VARIABLES ###
__slots__ = ('_contents',
'_current_window',
'_min_dynamic',
'_max_steps',
'_disable_rewrite_meter',
'_mask',
'_is_first_window',
'_time_signatures',
'_omit_time_signatures',
'_use_multimeasure_rests',
'_boundary_depth',
'_maximum_dot_count',
'_rewrite_tuplets',
'_process_on_first_call',
'_include_empty_measures',
'_repetition_chance',
'_prettify_rewrite_meter',
'_extract_trivial_tuplets',
'_fuse_across_groups_of_beats',
'_fuse_quadruple_meter',
'_fuse_triple_meter',
)
_dynamic_name_to_dynamic_ordinal = {
'ppppp': -5,
'pppp': -4,
'ppp': -3,
'pp': -2,
'p': -1,
'mp': 0,
'mf': 1,
'f': 2,
'ff': 3,
'fff': 4,
'ffff': 5,
'fffff': 6,
'fp': -1,
'sf': 2,
'sff': 3,
'sfp': -1,
'sfpp': -2,
'sffp': -1,
'sffpp': -2,
'sfz': 2,
'sp': -1,
'spp': -2,
'rfz': 2,
}
_dynamic_ordinal_to_dynamic_name = {
-5 : 'ppppp',
-4 : 'pppp',
-3 : 'ppp',
-2 : 'pp',
-1 : 'p',
0 : 'mp',
1 : 'mf',
2 : 'f',
3 : 'ff',
4 : 'fff',
5 : 'ffff',
6 : 'fffff',
}
### INITIALISER ###
[docs] def __init__(self,
contents: abjad.Container,
*,
min_dynamic: Union[abjad.Dynamic, str] = 'ppp',
max_steps: int = 1,
repetition_chance: float = 0.0,
process_on_first_call: bool = False,
disable_rewrite_meter: bool = False,
omit_time_signatures: bool = False,
use_multimeasure_rests: bool = True,
boundary_depth: Optional[int] = None,
maximum_dot_count: Optional[int] = None,
rewrite_tuplets: bool = True,
include_empty_measures: bool = True,
prettify_rewrite_meter: bool = True,
extract_trivial_tuplets: bool = True,
fuse_across_groups_of_beats: bool = True,
fuse_quadruple_meter: bool = True,
fuse_triple_meter: bool = True,
) -> None:
r'Initialises self.'
self.min_dynamic = min_dynamic
self.max_steps = max_steps
self.contents = contents
self.disable_rewrite_meter = disable_rewrite_meter
self.omit_time_signatures = omit_time_signatures
self.use_multimeasure_rests = use_multimeasure_rests
self.boundary_depth = boundary_depth
self.maximum_dot_count = maximum_dot_count
self.rewrite_tuplets = rewrite_tuplets
self.prettify_rewrite_meter = prettify_rewrite_meter
self.extract_trivial_tuplets = extract_trivial_tuplets
self.fuse_across_groups_of_beats = fuse_across_groups_of_beats
self.fuse_quadruple_meter = fuse_quadruple_meter
self.fuse_triple_meter = fuse_triple_meter
self.process_on_first_call = process_on_first_call
self.include_empty_measures = include_empty_measures
self.repetition_chance = repetition_chance
self._is_first_window = True
### SPECIAL METHODS ###
[docs] def __repr__(self) -> str:
r'Returns interpreter representation of :attr:`contents`.'
return abjad.lilypond(self._contents)
[docs] def __len__(self) -> int:
r'Returns the number of logical ties of :attr:`contents`.'
logical_ties = abjad.select(self._contents).logical_ties(pitched=True)
return len(logical_ties)
[docs] def __call__(self) -> abjad.Selection:
r"""Calls the echo process for one iteration, returning an
|abjad.Selection|.
"""
if (self._repetition_chance == 0.0
or random.random() > self._repetition_chance):
if not self._is_first_window or self._process_on_first_call:
self._soften_mask()
self._mask_to_selection()
return self.current_window
[docs] def __next__(self) -> abjad.Selection:
r"""Calls the echoing process for one iteration, returning an
|abjad.Selection|.
"""
if self._done:
raise StopIteration
return self.__call__()
[docs] def __iter__(self) -> None:
r'Returns an iterator, allowing instances to be used as iterators.'
return self
### PUBLIC METHODS ###
[docs] def output_all(self) -> abjad.Selection:
r"""Goes through the whole echoing process and outputs a single
|abjad.Selection|.
"""
dummy_container = abjad.Container()
while True:
dummy_container.append(self.__call__())
if self._done:
break
mutate.remove_repeated_time_signatures(dummy_container[:])
mutate.remove_repeated_dynamics(dummy_container[:])
output = dummy_container[:]
dummy_container[:] = []
return output
[docs] def output_n(self,
n: int,
) -> abjad.Selection:
r"""Goes through ``n`` iterations of the echoing process and outputs a
single |abjad.Selection|.
"""
if not isinstance(n, int):
raise TypeError("first positional argument must be 'int'")
if n < 1:
raise ValueError("first positional argument must be a positive "
"'int'")
dummy_container = abjad.Container()
for _ in range(n):
dummy_container.append(self.__call__())
mutate.remove_repeated_time_signatures(dummy_container[:])
mutate.remove_repeated_dynamics(dummy_container[:])
output = dummy_container[:]
dummy_container[:] = []
return output
[docs] def reset(self) -> None:
r'Resets the process, regenerating the mask.'
self._is_first_window = True
self._get_mask()
### PRIVATE METHODS ###
def _mask_to_selection(self) -> None:
r'Applies the mask to :attr:`contents`.'
dummy_container = abjad.mutate.copy(self._contents)
if not self._is_first_window or self._process_on_first_call:
logical_ties = abjad.select(dummy_container).logical_ties(
pitched=True,
)
dyn_list = self._mask_to_dyn_list()
previous_dyn = None
for logical_tie, new_dyn in zip(logical_ties, dyn_list):
current_dyn = abjad.get.indicator(logical_tie.head,
abjad.Dynamic,
)
if current_dyn is not None:
abjad.detach(current_dyn, logical_tie.head)
if new_dyn is not None:
if new_dyn != previous_dyn:
abjad.attach(new_dyn, logical_tie.head)
else:
self._convert_pitched_logical_tie_to_rest(logical_tie)
previous_dyn = new_dyn
# handling dynamics and slurs and empty tuplets
mutate.reposition_dynamics(dummy_container[:])
mutate.reposition_slurs(dummy_container[:])
mutate.extract_trivial_tuplets(dummy_container[:])
# applying rewrite meter
if not self._disable_rewrite_meter:
mutate.auto_rewrite_meter(
dummy_container,
boundary_depth=self._boundary_depth,
maximum_dot_count=self._maximum_dot_count,
rewrite_tuplets=self._rewrite_tuplets,
prettify_rewrite_meter=self._prettify_rewrite_meter,
extract_trivial_tuplets=self._extract_trivial_tuplets,
fuse_across_groups_of_beats=self._fuse_across_groups_of_beats,
fuse_quadruple_meter=self._fuse_quadruple_meter,
fuse_triple_meter=self._fuse_triple_meter,
)
if self._use_multimeasure_rests:
mutate.rests_to_multimeasure_rest(dummy_container[:])
# output
self._current_window = dummy_container[:]
dummy_container[:] = []
self._is_first_window = False
def _get_lilypond_format(self) -> str:
r'Returns interpreter representation of :attr:`contents`.'
return self.__repr__()
def _get_mask(self) -> None:
r'Creates a mask of dynamic ordinals from :attr:`contents`.'
logical_ties = abjad.select(self._contents).logical_ties(pitched=True)
self._mask = []
previous_dyn = None
for logical_tie in logical_ties:
dyn = abjad.get.indicator(logical_tie.head, abjad.Dynamic)
if dyn is not None:
try:
dyn = self._dynamic_name_to_dynamic_ordinal[dyn.name]
except KeyError as exc:
raise ValueError(f"the dynamic marking '{dyn.name}' is "
"not supported") from exc
else:
dyn = previous_dyn
self._mask.append(dyn)
previous_dyn = dyn
if self._mask[0] is None:
raise RuntimeError("first note of 'contents' must have a dynamic")
def _mask_to_dyn_list(self) -> list[str]:
r'Converts the numerical mask into a list of dynamic strings.'
dyn_list = []
for dyn_number in self._mask:
if dyn_number is not None:
dyn = self._dynamic_ordinal_to_dynamic_name[dyn_number]
dyn_list.append(abjad.Dynamic(dyn))
else:
dyn_list.append(None)
return dyn_list
def _soften_mask(self) -> list[Union[int, None]]:
r'Lowers the dynamics of the mask by one level.'
for n in range(random.randint(1, self._max_steps)):
if any(item is not None for item in self._mask):
self._mask = [self._soften_dynamic(item,
self._min_dynamic_number,
)
for item in self._mask]
elif n == 0:
raise RuntimeError("'current_window' is already empty")
@staticmethod
def _soften_dynamic(item: Union[int, None],
min_dyn_number: int,
) -> Union[int, None]:
r"""Lowers a numerical dynamic level by one, setting it to ``None`` if
dynamic below a given threshold.
"""
if item is None:
return None
item -= 1
if item < min_dyn_number:
return None
return item
@staticmethod
def _convert_pitched_logical_tie_to_rest(logical_tie) -> None:
r'Converts all leaves of a pitched logical tie into rests.'
indicators_tuple = (abjad.BarLine,
abjad.Clef,
abjad.Dynamic,
abjad.Fermata,
abjad.KeySignature,
abjad.LilyPondLiteral,
abjad.MetronomeMark,
abjad.Ottava,
abjad.RehearsalMark,
abjad.Repeat,
abjad.StaffChange,
abjad.StartHairpin,
abjad.StartMarkup,
abjad.StartPhrasingSlur,
abjad.StartSlur,
abjad.StartTextSpan,
abjad.StopHairpin,
abjad.StopPhrasingSlur,
abjad.StopSlur,
abjad.StopTextSpan,
abjad.TimeSignature,
)
for leaf in logical_tie:
rest = abjad.Rest(leaf.written_duration)
for indicator in abjad.get.indicators(leaf):
if isinstance(indicator, indicators_tuple):
abjad.attach(indicator, rest)
abjad.mutate.replace(leaf, rest)
@staticmethod
def _remove_all_time_signatures(container) -> None:
r'Removes all time signatures of an |abjad.Container|.'
for leaf in abjad.select(container).leaves():
if abjad.get.effective(leaf, abjad.TimeSignature):
abjad.detach(abjad.TimeSignature, leaf)
### PUBLIC PROPERTIES ###
@property
def contents(self) -> abjad.Container:
r'The |abjad.Container| to be faded.'
return abjad.mutate.copy(self._contents)
@contents.setter
def contents(self,
contents: abjad.Container,
) -> None:
if not isinstance(contents, abjad.Container):
raise TypeError("'contents' must be 'abjad.Container' or "
"child class")
if not abjad.select(contents).leaves().are_contiguous_logical_voice():
raise ValueError("'contents' must be contiguous logical voice")
if isinstance(contents, abjad.Score):
self._contents = abjad.mutate.copy(contents[0])
elif isinstance(contents, abjad.Tuplet):
self._contents = abjad.Container([abjad.mutate.copy(contents)])
else:
self._contents = abjad.mutate.copy(contents)
time_signatures = get.time_signature_list(
self._contents,
do_not_use_none=True,
)
mutate.enforce_time_signature(self._contents, time_signatures)
dummy_container = abjad.mutate.copy(contents)
self._current_window = dummy_container[:]
dummy_container[:] = []
self.reset()
self._is_first_window = True
@property
def current_window(self) -> abjad.Selection:
r'Read-only property, returns the previously output selection.'
current_window = abjad.mutate.copy(self._current_window)
if self._omit_time_signatures:
self._remove_all_time_signatures(current_window)
return current_window
@property
def min_dynamic(self) -> Union[abjad.Dynamic, str]:
r'The minimum dynamic below which notes are removed.'
return self._min_dynamic
@min_dynamic.setter
def min_dynamic(self,
min_dynamic: Union[abjad.Dynamic, str],
) -> None:
if not isinstance(min_dynamic, (abjad.Dynamic, str)):
raise TypeError("'min_dynamic' must be 'abjad.Dynamic' or 'str'")
if isinstance(min_dynamic, abjad.Dynamic):
self._min_dynamic = min_dynamic.name
else:
self._min_dynamic = min_dynamic
@property
def max_steps(self) -> int:
r'The maximum number of steps per operation.'
return self._max_steps
@max_steps.setter
def max_steps(self,
max_steps: int,
) -> None:
if not isinstance(max_steps, int):
raise TypeError("'max_steps' must be 'int'")
if max_steps < 1:
raise ValueError("'max_steps' must be greater than zero")
self._max_steps = max_steps
@property
def disable_rewrite_meter(self) -> bool:
r"""When ``True``, the durations of the notes in the output will not be
rewritten by the |abjad.Meter.rewrite_meter()| mutation.
"""
return self._disable_rewrite_meter
@disable_rewrite_meter.setter
def disable_rewrite_meter(self,
disable_rewrite_meter: bool,
) -> None:
if not isinstance(disable_rewrite_meter, bool):
raise TypeError("'disable_rewrite_meter' must be 'bool'")
self._disable_rewrite_meter = disable_rewrite_meter
@property
def omit_time_signatures(self) -> bool:
r'When ``True``, all time signatures will be omitted from the output.'
return self._omit_time_signatures
@omit_time_signatures.setter
def omit_time_signatures(self,
omit_time_signatures: bool,
) -> None:
if not isinstance(omit_time_signatures, bool):
raise TypeError("'omit_time_signatures' must be 'bool'")
self._omit_time_signatures = omit_time_signatures
@property
def use_multimeasure_rests(self) -> bool:
r'When ``True``, multi-measure rests will be used for silent measures.'
return self._use_multimeasure_rests
@use_multimeasure_rests.setter
def use_multimeasure_rests(self,
use_multimeasure_rests: bool,
) -> None:
if not isinstance(use_multimeasure_rests, bool):
raise TypeError("'use_multimeasure_rests' must be 'bool'")
self._use_multimeasure_rests = use_multimeasure_rests
@property
def boundary_depth(self) -> Union[int, None]:
r"""Sets the argument ``boundary_depth`` of
|abjad.Meter.rewrite_meter()|.
"""
return self._boundary_depth
@boundary_depth.setter
def boundary_depth(self,
boundary_depth: Optional[int],
) -> None:
if boundary_depth is not None:
if not isinstance(boundary_depth, int):
raise TypeError("'boundary_depth' must be 'int'")
self._boundary_depth = boundary_depth
@property
def maximum_dot_count(self) -> Union[int, None]:
r"""Sets the argument ``maximum_dot_count`` of
|abjad.Meter.rewrite_meter()|.
"""
return self._maximum_dot_count
@maximum_dot_count.setter
def maximum_dot_count(self,
maximum_dot_count: Optional[int],
) -> None:
if maximum_dot_count is not None:
if not isinstance(maximum_dot_count, int):
raise TypeError("'maximum_dot_count' must be 'int'")
self._maximum_dot_count = maximum_dot_count
@property
def rewrite_tuplets(self) -> bool:
r"""Sets the argument ``rewrite_tuplets`` of
|abjad.Meter.rewrite_meter()|.
"""
return self._rewrite_tuplets
@rewrite_tuplets.setter
def rewrite_tuplets(self,
rewrite_tuplets: bool,
) -> None:
if not isinstance(rewrite_tuplets, bool):
raise TypeError("'rewrite_tuplets' must be 'bool'")
self._rewrite_tuplets = rewrite_tuplets
@property
def prettify_rewrite_meter(self) -> bool:
r"""Used to enable or disable the mutation
|auxjad.mutate.prettify_rewrite_meter()| (default ``True``).
"""
return self._prettify_rewrite_meter
@prettify_rewrite_meter.setter
def prettify_rewrite_meter(self,
prettify_rewrite_meter: bool,
) -> None:
if not isinstance(prettify_rewrite_meter, bool):
raise TypeError("'prettify_rewrite_meter' must be 'bool'")
self._prettify_rewrite_meter = prettify_rewrite_meter
@property
def extract_trivial_tuplets(self) -> bool:
r"""Sets the argument ``extract_trivial_tuplets`` of
|auxjad.mutate.prettify_rewrite_meter()|.
"""
return self._extract_trivial_tuplets
@extract_trivial_tuplets.setter
def extract_trivial_tuplets(self,
extract_trivial_tuplets: bool,
) -> None:
if not isinstance(extract_trivial_tuplets, bool):
raise TypeError("'extract_trivial_tuplets' must be 'bool'")
self._extract_trivial_tuplets = extract_trivial_tuplets
@property
def fuse_across_groups_of_beats(self) -> bool:
r"""Sets the argument ``fuse_across_groups_of_beats`` of
|auxjad.mutate.prettify_rewrite_meter()|.
"""
return self._fuse_across_groups_of_beats
@fuse_across_groups_of_beats.setter
def fuse_across_groups_of_beats(self,
fuse_across_groups_of_beats: bool,
) -> None:
if not isinstance(fuse_across_groups_of_beats, bool):
raise TypeError("'fuse_across_groups_of_beats' must be 'bool'")
self._fuse_across_groups_of_beats = fuse_across_groups_of_beats
@property
def fuse_quadruple_meter(self) -> bool:
r"""Sets the argument ``fuse_quadruple_meter`` of
|auxjad.mutate.prettify_rewrite_meter()|.
"""
return self._fuse_quadruple_meter
@fuse_quadruple_meter.setter
def fuse_quadruple_meter(self,
fuse_quadruple_meter: bool,
) -> None:
if not isinstance(fuse_quadruple_meter, bool):
raise TypeError("'fuse_quadruple_meter' must be 'bool'")
self._fuse_quadruple_meter = fuse_quadruple_meter
@property
def fuse_triple_meter(self) -> bool:
r"""Sets the argument ``fuse_triple_meter`` of
|auxjad.mutate.prettify_rewrite_meter()|.
"""
return self._fuse_triple_meter
@fuse_triple_meter.setter
def fuse_triple_meter(self,
fuse_triple_meter: bool,
) -> None:
if not isinstance(fuse_triple_meter, bool):
raise TypeError("'fuse_triple_meter' must be 'bool'")
self._fuse_triple_meter = fuse_triple_meter
@property
def process_on_first_call(self) -> bool:
r"""If ``True`` then :attr:`contents` will be processed in the very
first call.
"""
return self._process_on_first_call
@process_on_first_call.setter
def process_on_first_call(self,
process_on_first_call: bool,
) -> None:
if not isinstance(process_on_first_call, bool):
raise TypeError("'process_on_first_call' must be 'bool'")
self._process_on_first_call = process_on_first_call
@property
def include_empty_measures(self) -> bool:
r"""If ``True`` then an initial or final empty measures will be used,
otherwise the process starts/ends with a single logical tie.
"""
return self._include_empty_measures
@include_empty_measures.setter
def include_empty_measures(self,
include_empty_measures: bool,
) -> None:
if not isinstance(include_empty_measures, bool):
raise TypeError("'include_empty_measures' must be 'bool'")
self._include_empty_measures = include_empty_measures
@property
def repetition_chance(self) -> float:
r"""The chance of not processing :attr:`contents` on a call, thus
repeating the previous output.
"""
return self._repetition_chance
@repetition_chance.setter
def repetition_chance(self,
repetition_chance: float,
) -> None:
if not isinstance(repetition_chance, float):
raise TypeError("'repetition_chance' must be 'float'")
if repetition_chance < 0.0 or repetition_chance > 1.0:
raise ValueError("'repetition_chance' must be between 0.0 and 1.0")
self._repetition_chance = repetition_chance
### PRIVATE PROPERTIES ###
@property
def _done(self) -> bool:
r""":obj:`bool` indicating whether the process is done, which is when
the mask is filled with ``None``'s.
"""
if self._include_empty_measures:
return all(item is None for item in self._mask)
else:
return all(item in (None, self._min_dynamic_number)
for item in self._mask)
@property
def _min_dynamic_number(self) -> int:
r'Numerical representation of :attr:`min_dynamic`.'
return self._dynamic_name_to_dynamic_ordinal[self._min_dynamic]