import random
from typing import Optional, Union
import abjad
from .. import get, mutate
[docs]class Phaser():
r"""Takes an |abjad.Container| (or child class) as input and outputs an
|abjad.Selection| with leaves shifted by a fixed amount. Subsequent calls
apply further shifts.
Basic usage:
Calling the object will return an |abjad.Selection| generated by
the phasing process. Each call of the object will shift the contents of
the input container by a fixed amount given by :attr:`step_size`. Note
that, by default, the first call outputs the initial container, with
subsequent calls phasing it.
>>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
>>> abjad.show(container)
.. docs::
{
c'4
d'4
e'4
f'4
}
.. figure:: ../_images/Phaser-VjQH8XsyZ6.png
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 16),
... )
>>> notes = phaser()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
d'4
e'4
f'4
}
.. figure:: ../_images/Phaser-953j4yo4hlo.png
>>> notes = phaser()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'8.
d'16
~
d'8.
e'16
~
e'8.
f'16
~
f'8.
c'16
}
.. figure:: ../_images/Phaser-3l0a96vbfbi.png
The property :attr:`current_window` can be used to access the current
window without moving the head forwards.
>>> notes = phaser.current_window()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'8.
d'16
~
d'8.
e'16
~
e'8.
f'16
~
f'8.
c'16
}
.. figure:: ../_images/Phaser-pn2qf2z6vzr.png
:attr:`process_on_first_call`:
The very first call will output the input container without processing
it. To disable this behaviour and phase 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 d'4 e'4 f'4")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 16),
... process_on_first_call=True,
... )
>>> notes = phaser()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'8.
d'16
~
d'8.
e'16
~
e'8.
f'16
~
f'8.
c'16
}
.. figure:: ../_images/Phaser-fx0ywlw5u25.png
:attr:`step_size`:
The argument :attr:`step_size` is used to step sizes for the phasing
process. It takes a :obj:`tuple` or an |abjad.Duration|.
>>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 8),
... )
>>> notes = phaser()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
d'4
e'4
f'4
}
.. figure:: ../_images/Phaser-zfxas2f5o87.png
>>> notes = phaser()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'8
d'4
e'8
~
e'8
f'4
c'8
}
.. figure:: ../_images/Phaser-2e9dzvji4wg.png
Using as iterator:
The instances of :class:`Phaser` can also be used as an iterator, which
can then be used in a for loop to phase through a full cycle. Note that
unlike the methods :meth:`output_n` and :meth:`output_all`, time
signatures are added to each window returned by the shuffler. Use the
function |auxjad.mutate.remove_repeated_time_signatures()| to clean
the output when using :class:`Phaser` in this way.
>>> container = abjad.Container(r"\time 3/4 c'4 d'4 e'4 ~ e'2.")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 4),
... )
>>> staff = abjad.Staff()
>>> for window in phaser:
... staff.append(window)
>>> auxjad.mutate.remove_repeated_time_signatures(staff)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/4
c'4
d'4
e'4
~
e'2.
d'4
e'2
~
e'2
c'4
e'2.
~
e'4
c'4
d'4
e'2.
c'4
d'4
e'4
e'2
c'4
d'4
e'2
e'4
c'4
d'4
e'2.
}
.. figure:: ../_images/Phaser-jd1h0w8pbpp.png
Arguments and properties:
This class can take many optional keyword arguments during its
creation. :attr:`max_steps` sets the maximum number of steps that can
be applied for a single call, ranging between ``1`` and the input value
(default is also ``1``); if :attr:`max_steps` is set to a value larger
than 1, the number of steps at every call will be randomly chosen.
:attr:`forward_bias` sets the chance of the process moving forward
instead of backwards. It should range from ``0.0`` to ``1.0`` (default
``1.0``, which means the process can only move forwards. A value of
``0.5`` gives 50% chance of moving forwards while a value of ``0.0``
will result in the process moving only backwards). By default, when a
logical tie is split in between windows, any unterminated ties will be
removed; set :attr:`remove_unterminated_ties` 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. By default, calling the object will first return the
original container and subsequent calls will process it; set
:attr:`process_on_first_call` to ``True`` and the looping process will
be applied on the very first call. Setting the property
:attr:`omit_time_signatures` to ``True`` will remove all time
signatures from the output (``False`` by default).
>>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
>>> phaser = auxjad.Phaser(container,
... step_size=(5, 8),
... max_steps=2,
... forward_bias=0.2,
... remove_unterminated_ties=True,
... omit_time_signatures=True,
... boundary_depth=0,
... maximum_dot_count=1,
... rewrite_tuplets=False,
... process_on_first_call=True,
... )
>>> phaser.step_size
5/8
>>> phaser.max_steps
2
>>> phaser.forward_bias
0.2
>>> phaser.remove_unterminated_ties
True
>>> phaser.omit_time_signatures
True
>>> phaser.boundary_depth
0
>>> phaser.maximum_dot_count
1
>>> phaser.rewrite_tuplets
False
>>> phaser.process_on_first_call
True
Use the properties below to change these values after initialisation.
>>> phaser.step_size = (1, 4)
>>> phaser.max_steps = 3
>>> phaser.forward_bias = 0.8
>>> phaser.remove_unterminated_ties = False
>>> phaser.omit_time_signatures = False
>>> phaser.boundary_depth = 1
>>> phaser.maximum_dot_count = 2
>>> phaser.rewrite_tuplets = True
>>> phaser.process_on_first_call = False
>>> phaser.step_size
1/4
>>> phaser.max_steps
3
>>> phaser.forward_bias
0.8
>>> phaser.remove_unterminated_ties
False
>>> phaser.omit_time_signatures
False
>>> phaser.boundary_depth
1
>>> phaser.maximum_dot_count
2
>>> phaser.rewrite_tuplets
True
>>> phaser.process_on_first_call
False
Setting :attr:`forward_bias` to ``0.0``:
Set :attr:`forward_bias` to ``0.0`` to move backwards instead of
forwards (default is ``1.0``).
>>> container = abjad.Container(r"\time 3/8 c'8 d'8 e'8")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 16),
... )
>>> notes = phaser.output_n(3)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/8
c'8
d'8
e'8
c'16
d'8
e'8
c'16
d'8
e'8
c'8
}
.. figure:: ../_images/Phaser-x1gn7mv4jq.png
>>> container = abjad.Container(r"\time 3/8 c'8 d'8 e'8")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 16),
... forward_bias=0.0,
... )
>>> notes = phaser.output_n(3)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/8
c'8
d'8
e'8
e'16
c'8
d'8
e'16
e'8
c'8
d'8
}
.. figure:: ../_images/Phaser-g6y94qiwfro.png
:attr:`forward_bias` between ``0.0`` and ``1.0``:
Setingt :attr:`forward_bias` to a value in between ``0.0`` and ``1.0``
will result in random steps being taken forward or backward, according
to the bias.
>>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 4),
... forward_bias=0.5,
... )
>>> notes = phaser.output_n(5)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
d'4
e'4
f'4
d'4
e'4
f'4
c'4
c'4
d'4
e'4
f'4
f'4
c'4
d'4
e'4
c'4
d'4
e'4
f'4
}
.. figure:: ../_images/Phaser-lu2p8o1g6p.png
: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'8 d'8 e'8 f'8 g'8 a'8 b'8 c''8")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 8),
... max_steps=4,
... )
>>> notes = phaser.output_n(5)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'8
d'8
e'8
f'8
g'8
a'8
b'8
c''8
f'8
g'8
a'8
b'8
c''8
c'8
d'8
e'8
c''8
c'8
d'8
e'8
f'8
g'8
a'8
b'8
f'8
g'8
a'8
b'8
c''8
c'8
d'8
e'8
a'8
b'8
c''8
c'8
d'8
e'8
f'8
g'8
}
.. figure:: ../_images/Phaser-pvbugjwu5qd.png
:func:`len()`:
The function :func:`len()` can be used to get the total number of steps
that are necessary to return to the initial container.
>>> container = abjad.Container(r"c'1")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 16),
... )
>>> len(phaser)
16
>>> container = abjad.Container(r"c'1")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 4),
... )
>>> len(phaser)
4
>>> container = abjad.Container(r"\time 3/4 c'2.")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 2),
... )
>>> len(phaser)
3
:meth:`output_all`:
To run through the whole process and output it as a single container,
from the initial head position until the process outputs the single
last element, use the method :meth:`output_all`.
>>> container = abjad.Container(r"\time 3/4 c'4. d'4.")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 4),
... )
>>> notes = phaser.output_all()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/4
c'4.
d'4.
c'8
d'4.
c'4
d'4
c'4.
d'8
c'4.
d'4.
}
.. figure:: ../_images/Phaser-y6r36v26mk7.png
``cycle_back_to_first``:
By default, :meth:`output_all` will cycle back to the very first
window. To stop at the iteration step just before looping back to the
initial container, set the keyword argument ``cycle_back_to_first`` to
``False``.
>>> container = abjad.Container(r"\time 3/4 c'4. d'4.")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 4),
... )
>>> notes = phaser.output_all(cycle_back_to_first=False)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/4
c'4.
d'4.
c'8
d'4.
c'4
d'4
c'4.
d'8
}
.. figure:: ../_images/Phaser-1lc6vkz1wuf.png
:meth:`output_n`:
To run through just part of the process and output it as a single
container, starting from the initial head position, use the method
:meth:`output_n` and pass the number of iterations as argument.
>>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 32),
... )
>>> notes = phaser.output_n(3)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
d'4
e'4
f'4
c'8..
d'32
~
d'8..
e'32
~
e'8..
f'32
~
f'8..
c'32
c'8.
d'16
~
d'8.
e'16
~
e'8.
f'16
~
f'8.
c'16
}
.. figure:: ../_images/Phaser-ii58nvyi3c.png
``tie_identical_pitches``:
Both :meth:`output_n` and :meth:`output_all` methods can receive the
keyword argument ``tie_identical_pitches``, which will tie identical
pitches across iterations of the process when set to ``True`` (default
is ``False``). Compare the output below with the example just above.
>>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 32),
... )
>>> notes = phaser.output_n(3, tie_identical_pitches=True)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
d'4
e'4
f'4
c'8..
d'32
~
d'8..
e'32
~
e'8..
f'32
~
f'8..
c'32
~
c'8.
d'16
~
d'8.
e'16
~
e'8.
f'16
~
f'8.
c'16
}
.. figure:: ../_images/Phaser-gvzlwz9gol6.png
:attr:`remove_unterminated_ties`:
All methods that call the phasing process (:meth:`__call__`,
:meth:`__next__`, :meth:`output_all`, :meth:`output_n`) remove
unterminated ties at the end of a selection, which are a result of a
logical tie being split at that point. Use the optional keyword
argument ``remove_unterminated_ties=False`` when initialising the
phaser to disable this behaviour.
>>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 16),
... process_on_first_call=True,
... remove_unterminated_ties=False,
... )
>>> notes = phaser()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'8.
d'16
~
d'8.
e'16
~
e'8.
f'16
~
f'8.
c'16
~
}
.. figure:: ../_images/Phaser-hg27xwbz4t.png
Time signature changes:
This class handles time signature changes.
>>> container = abjad.Container(
... r"\time 2/4 c'2 \time 3/8 d'4. \time 2/4 e'2"
... )
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 8),
... )
>>> notes = phaser.output_n(3)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 2/4
c'2
\time 3/8
d'4.
\time 2/4
e'2
c'4.
d'8
~
\time 3/8
d'4
e'8
~
\time 2/4
e'4.
c'8
c'4
d'4
~
\time 3/8
d'8
e'4
~
\time 2/4
e'4
c'4
}
.. figure:: ../_images/Phaser-vw3gfizbkg.png
Indicators:
This class can handle dynamics and articulations too. When a logical
tie is split into two during the phasing process, dynamics and
articulations are passed on to both of them.
>>> container = abjad.Container(r"c'4-.\p\< d'4--\f e'4->\p f'4")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 8),
... )
>>> staff = abjad.Staff()
>>> notes = phaser.output_n(5)
>>> staff.append(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
\p
- \staccato
\<
d'4
\f
- \tenuto
e'4
\p
- \accent
f'4
c'8
- \staccato
\<
d'4
\f
- \tenuto
e'8
\p
- \accent
~
e'8
f'4
c'8
- \staccato
d'4
\f
- \tenuto
e'4
\p
- \accent
f'4
c'4
- \staccato
d'8
\f
- \tenuto
e'4
\p
- \accent
f'8
~
f'8
c'4
- \staccato
d'8
\f
- \tenuto
e'4
\p
- \accent
f'4
c'4
- \staccato
\<
d'4
\f
- \tenuto
}
.. figure:: ../_images/Phaser-978yobu5f2q.png
Slurs and hairpins:
Slurs and hairpins are also supported.
>>> container = abjad.Container(
... r"c'2(\p\< d'4. e'8\f f'4\p\> g'2 a'4\pp)"
... )
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 16),
... )
>>> notes = phaser.output_n(5)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'2
\p
\<
(
d'4.
e'8
\f
f'4
\p
\>
g'2
a'4
\pp
)
c'4..
\p
\<
(
d'16
~
d'4
~
d'16
e'8
\f
f'16
\p
~
f'8.
\>
g'16
~
g'4
~
g'8.
a'16
\pp
~
a'8.
)
c'16
\p
(
c'4.
\<
d'8
~
d'4
e'8
\f
f'8
\p
~
f'8
\>
g'8
~
g'4
~
g'8
a'4
\pp
)
c'8
\p
(
c'4
~
c'16
\<
d'8.
~
d'8.
e'16
\f
~
e'16
f'8.
\p
~
f'16
\>
g'8.
~
g'4
~
g'16
a'8.
\pp
~
a'16
)
c'8.
\p
(
c'4
\<
d'4
~
d'8
e'8
\f
f'4
\p
\>
g'2
a'4
\pp
)
c'4
\p
}
.. figure:: ../_images/Phaser-pxj7axmh58e.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 fading process has ended.
:attr:`contents`:
Use the :attr:`contents` property to read as well as overwrite the
contents of the phaser. Notice that the phasing process will start from
the beginning of the new container.
>>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 16),
... )
>>> notes = phaser()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
d'4
e'4
f'4
}
.. figure:: ../_images/Phaser-ns0dbcanp5.png
>>> notes = phaser()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'8.
d'16
~
d'8.
e'16
~
e'8.
f'16
~
f'8.
c'16
}
.. figure:: ../_images/Phaser-utcxb4w611s.png
>>> phaser.contents = abjad.Container(r"c'16 d'16 e'16 f'16 g'2.")
>>> notes = phaser()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'16
d'16
e'16
f'16
g'2.
}
.. figure:: ../_images/Phaser-w0whe6c7jp.png
>>> notes = phaser()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
d'16
e'16
f'16
g'16
~
g'2
~
g'8.
c'16
}
.. figure:: ../_images/Phaser-cmo73lo14wo.png
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. d'8 e'2")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 16),
... )
>>> notes = phaser()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4.
d'8
e'2
}
.. figure:: ../_images/Phaser-uhfkf8mzu4i.png
Set :attr:`boundary_depth` to a different number to change its
behaviour.
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 16),
... boundary_depth=1,
... )
>>> notes = phaser()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
~
c'8
d'8
e'2
}
.. figure:: ../_images/Phaser-l6pvwhsj1hh.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.
: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 3/4 c'4 d'4 e'4")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 8),
... omit_time_signatures=True,
... )
>>> notes = phaser.output_n(3)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'4
d'4
e'4
c'8
d'4
e'4
c'8
d'4
e'4
c'4
}
.. figure:: ../_images/Phaser-1rdkecll151.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.
.. warning::
This class can handle tuplets, but the output is often quite complex.
Although the result will be rhythmically correct, consecutive tuplets
are not fused together, and tuplets may be output off-beat. This
functionality should be considered experimental.
>>> container = abjad.Container(r"\times 2/3 {c'8 d'8 e'8} d'2.")
>>> phaser = auxjad.Phaser(container,
... step_size=(1, 16),
... )
>>> notes = phaser.output_n(3)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\times 2/3
{
\time 4/4
c'8
d'8
e'8
}
d'2.
\times 2/3
{
c'32
d'16
~
d'16
e'8
}
d'16
~
d'2
~
d'8.
c'16
\times 2/3
{
d'16
e'8
}
d'8
~
d'2
~
d'8
\times 2/3
{
c'8
d'16
}
}
.. figure:: ../_images/Phaser-lqvc5p5i25b.png
"""
### CLASS VARIABLES ###
__slots__ = ('_contents',
'_pivot_point',
'_step_size',
'_max_steps',
'_forward_bias',
'_remove_unterminated_ties',
'_current_window',
'_is_first_window',
'_contents_length',
'_omit_time_signatures',
'_boundary_depth',
'_maximum_dot_count',
'_rewrite_tuplets',
'_process_on_first_call',
'_prettify_rewrite_meter',
'_extract_trivial_tuplets',
'_fuse_across_groups_of_beats',
'_fuse_quadruple_meter',
'_fuse_triple_meter',
)
### INITIALISER ###
[docs] def __init__(self,
contents: abjad.Container,
*,
step_size: Union[int,
float,
str,
tuple[int],
abjad.Duration,
],
max_steps: int = 1,
forward_bias: float = 1.0,
process_on_first_call: bool = False,
remove_unterminated_ties: bool = True,
omit_time_signatures: bool = False,
boundary_depth: Optional[int] = None,
maximum_dot_count: Optional[int] = None,
rewrite_tuplets: 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.contents = contents
self._pivot_point = abjad.Duration(0)
self.step_size = step_size
self.max_steps = max_steps
self.forward_bias = forward_bias
self.remove_unterminated_ties = remove_unterminated_ties
self.omit_time_signatures = omit_time_signatures
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._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 length of :attr:`contents` in terms of
:attr:`step_size`.
"""
proportion = self._contents_length / self._step_size
return int(proportion * proportion.denominator)
[docs] def __call__(self) -> abjad.Selection:
r"""Calls the phaser process for one iteration, returning an
|abjad.Selection|.
"""
if not self._is_first_window or self._process_on_first_call:
self._move_pivot_point()
self._make_music()
self._is_first_window = False
return self.current_window
[docs] def __next__(self) -> abjad.Selection:
r"""Calls the phaser process for one iteration, returning an
|abjad.Selection|.
"""
if not self._is_first_window or self._process_on_first_call:
self._move_pivot_point()
if self._done:
raise StopIteration
self._make_music()
self._is_first_window = False
return self.current_window
[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,
*,
cycle_back_to_first: bool = True,
tie_identical_pitches: bool = False,
) -> abjad.Selection:
r"""Goes through the whole phasing process and outputs a single
|abjad.Selection|.
"""
if not isinstance(cycle_back_to_first, bool):
raise TypeError("'cycle_back_to_first' must be 'bool'")
if not isinstance(tie_identical_pitches, bool):
raise TypeError("'tie_identical_pitches' must be 'bool'")
dummy_container = abjad.Container()
while True:
selection = self.__call__()
if not self._done:
if tie_identical_pitches:
self._tie_identical_pitches(selection, dummy_container)
dummy_container.append(selection)
else:
break
if cycle_back_to_first:
if tie_identical_pitches:
self._tie_identical_pitches(selection, dummy_container)
dummy_container.append(selection)
mutate.remove_repeated_time_signatures(dummy_container[:])
mutate.reposition_dynamics(dummy_container[:])
mutate.reposition_slurs(dummy_container[:])
output = dummy_container[:]
dummy_container[:] = []
return output
[docs] def output_n(self,
n: int,
*,
tie_identical_pitches: bool = False,
) -> abjad.Selection:
r"""Goes through ``n`` iterations of the phasing 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'")
if not isinstance(tie_identical_pitches, bool):
raise TypeError("'tie_identical_pitches' must be 'bool'")
dummy_container = abjad.Container()
for _ in range(n):
selection = self.__call__()
if tie_identical_pitches:
self._tie_identical_pitches(selection, dummy_container)
dummy_container.append(selection)
mutate.remove_repeated_time_signatures(dummy_container[:])
mutate.reposition_dynamics(dummy_container[:])
mutate.reposition_slurs(dummy_container[:])
output = dummy_container[:]
dummy_container[:] = []
return output
### PRIVATE METHODS ###
def _move_pivot_point(self) -> None:
r"""Moves the pivot point by a certain number of steps of fixed size,
either forwards or backwards according to the forward bias.
"""
step = self._step_size * random.randint(1, self._max_steps)
diretion = self._biased_choice(self._forward_bias)
self._pivot_point += step * diretion
def _make_music(self) -> None:
r'Applies the phasing process and handles the output container.'
dummy_container = self._phase_contents()
# dealing with dynamics
mutate.reposition_dynamics(dummy_container[:])
# adding time signatures back and rewriting meter
time_signatures = get.time_signature_list(
self._contents,
do_not_use_none=True,
)
mutate.enforce_time_signature(
dummy_container,
time_signatures,
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,
)
self._current_window = dummy_container[:]
dummy_container[:] = []
def _phase_contents(self) -> abjad.Container:
r"""This method phases :attr:`contents` using ``_pivot_point`` as the
pivot point.
"""
dummy_container = abjad.mutate.copy(self._contents)
pivot = self._pivot_point % self._contents_length
# splitting leaves at both slicing points
if pivot > abjad.Duration(0):
abjad.mutate.split(dummy_container[:], [pivot])
# finding start and end indeces for the window
for start in range(len(dummy_container)):
if (abjad.get.duration(dummy_container[:start + 1]) > pivot):
break
last_leaf = dummy_container[:start].leaf(-1)
# copying indicators to both leaves
indicators_tuple = (abjad.Articulation,
abjad.Clef,
abjad.Dynamic,
abjad.Fermata,
abjad.KeySignature,
abjad.LilyPondLiteral,
abjad.MetronomeMark,
abjad.Ottava,
abjad.StaffChange,
abjad.StartHairpin,
abjad.StartPhrasingSlur,
abjad.StartSlur,
abjad.StopHairpin,
abjad.StopPhrasingSlur,
abjad.StopSlur,
)
if abjad.get.indicator(last_leaf, abjad.Tie): # i.e. split
for indicator in abjad.get.indicators(last_leaf):
if isinstance(indicator, indicators_tuple):
first_leaf = dummy_container[start:].leaf(0)
abjad.attach(indicator, first_leaf)
# removing ties of splitted logical tie if necessary
if (self._remove_unterminated_ties
and abjad.get.indicator(last_leaf, abjad.Tie)):
abjad.detach(abjad.Tie, last_leaf)
# joining two subcontainers
dummy_end_container = abjad.Container(
abjad.mutate.copy(dummy_container[:start])
)
dummy_container = abjad.Container(
abjad.mutate.copy(dummy_container[start:])
)
dummy_container.extend(dummy_end_container)
dummy_end_container[:] = []
return dummy_container
def _get_lilypond_format(self) -> str:
r'Returns interpreter representation of :attr:`contents`.'
return self.__repr__()
@staticmethod
def _tie_identical_pitches(currrent_selection,
previous_container,
) -> None:
r'Ties identical pitches when joining windows.'
if len(previous_container) == 0:
return
first_leaf = currrent_selection.leaf(0)
last_leaf = abjad.select(previous_container).leaf(-1)
if (get.leaves_are_tieable((first_leaf, last_leaf)) and not
abjad.get.indicator(last_leaf, abjad.Tie)):
abjad.attach(abjad.Tie(), last_leaf)
@staticmethod
def _biased_choice(bias) -> None:
r'Returns either +1 or -1 according to a bias value.'
return random.choices([1, -1], weights=[bias, 1.0 - bias])[0]
@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 phased.'
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)
dummy_container = abjad.mutate.copy(contents)
self._current_window = dummy_container[:]
dummy_container[:] = []
self._contents_length = abjad.get.duration(self._contents[:])
self._pivot_point = abjad.Duration(0)
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 pivot_point(self) -> abjad.Duration:
r'Read-only property, returns the position of the pivot point.'
return self._pivot_point
@property
def step_size(self) -> abjad.Duration:
r'The size of each step when moving the pivot point.'
return self._step_size
@step_size.setter
def step_size(self,
step_size: Union[int,
float,
str,
tuple[int],
abjad.Duration,
],
) -> None:
if not isinstance(step_size, (abjad.Duration, str, tuple, int, float)):
raise TypeError("'step_size' must be 'abjad.Duration', 'str', "
"'tuple', or a number")
self._step_size = abjad.Duration(step_size)
@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 forward_bias(self) -> float:
r"""The chance of the window moving forward instead of backwards. It
should range from 0.0 to 1.0 (default 1.0, which means the window can
only move forwards. A value of 0.5 gives 50% chance of moving forwards
while a value of 0.0 will move the window only backwards).
"""
return self._forward_bias
@forward_bias.setter
def forward_bias(self,
forward_bias: float,
) -> None:
if not isinstance(forward_bias, float):
raise TypeError("'forward_bias' must be 'float'")
if forward_bias < 0.0 or forward_bias > 1.0:
raise ValueError("'forward_bias' must be between 0.0 and 1.0")
self._forward_bias = forward_bias
@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 remove_unterminated_ties(self) -> bool:
r"""When ``True``, the last element of the |abjad.Selection| returned
by a call will have any ties removed. This means that splitted logical
ties will not tie accross multiple calls.
"""
return self._remove_unterminated_ties
@remove_unterminated_ties.setter
def remove_unterminated_ties(self,
remove_unterminated_ties: bool,
) -> None:
if not isinstance(remove_unterminated_ties, bool):
raise TypeError("'remove_unterminated_ties' must be 'bool'")
self._remove_unterminated_ties = remove_unterminated_ties
@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
### PRIVATE PROPERTIES ###
@property
def _done(self) -> bool:
r""":obj:`bool` indicating whether the process is done (i.e. whether
the pivot point has overtaken the :attr:`contents`'s length). Only
:meth:`__next__` and :meth:`output_all` make use of it, since regular
calls make use of the module of the position of the pivot point in
relation to the duration of :attr:`contents`, allowing for infinitely
many calls.
"""
return (self._pivot_point >= self._contents_length
or self._pivot_point <= -self._contents_length)