import random
from typing import Any, Optional, Union
import abjad
from .. import get, mutate, select
[docs]class Shuffler:
r"""Takes an |abjad.Container| (or child class) and shuffles or rotates its
logical ties or pitches. When shuffling or rotating pitches only, tuplets
are supported, otherwise tuplets are not supported.
Basic usage:
Calling the object will output a shuffled selection of the input
container.
>>> 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/Shuffler-1oLQZeQNPo.png
>>> shuffler = auxjad.Shuffler(container)
>>> notes = shuffler()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
d'4
c'4
f'4
e'4
}
.. figure:: ../_images/Shuffler-z2om98675v.png
>>> notes = shuffler()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
e'4
d'4
f'4
}
.. figure:: ../_images/Shuffler-xu7sln4vt7n.png
To get the result of the last operation, use the property
:attr:`current_window`.
>>> notes = shuffler.current_window
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
e'4
d'4
f'4
}
.. figure:: ../_images/Shuffler-gphtpqn9jb.png
Calling the object outputs the same result as using the method
:meth:`shuffle`.
>>> notes = shuffler.shuffle()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
e'4
f'4
c'4
d'4
}
.. figure:: ../_images/Shuffler-g965k0d03if.png
.. warning::
Unlike the other classes in Auxjad, the very first call of an instance
of this class will already process the initial container. To disable
this behaviour and output the initial container once before shuffling
or rotating it, initialise the class with the keyword argument
:attr:`process_on_first_call` set to ``False``.
>>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
>>> shuffler = auxjad.Shuffler(container,
... process_on_first_call=False,
... )
>>> notes = shuffler()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'4
d'4
e'4
f'4
}
.. figure:: ../_images/Shuffler-76039tn5b9k.png
:func:`len()`:
Applying the :func:`len()` function to the shuffler will return the
number of logical ties of :attr:`contents`.
>>> container = abjad.Container(r"c'4 d'4 e'4 f'4 ~ | f'2 g'2")
>>> shuffler = auxjad.Shuffler(container)
>>> len(shuffler)
5
Do note that consecutive rests are considered as a single logical tie,
so in the example below the :func:`len()` function returns ``5`` and
not ``6``. When shuffling or rotating logical ties, consecutive rests
are also shuffled and rotated together.
>>> container = abjad.Container(r"c'8. d'4 r8 r8. e'16 f'8.")
>>> shuffler = auxjad.Shuffler(container)
>>> len(shuffler)
5
Arguments and properties:
This class has many keyword arguments, all of which can be altered
after instantiation using properties with the same names as shown
below. Setting :attr:`pitch_only` to ``True`` will enable pitch mode;
by default, this class shuffles and rotates logical ties, but in pitch
mode only pitches are shuffled or rotated. By setting
:attr:`preserve_rest_position` to ``True`` the shuffle and rotation
operations will not change the position or duration of rests.
: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). 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 shuffling process
will be applied on the very first call.
>>> container = abjad.Container(
... r"\time 3/4 c'4 d'4 e'4 \time 2/4 f'4 g'4"
... )
>>> shuffler = auxjad.Shuffler(container,
... pitch_only=False,
... preserve_rest_position=True,
... disable_rewrite_meter=False,
... omit_time_signatures=True,
... boundary_depth=0,
... maximum_dot_count=1,
... rewrite_tuplets=False,
... process_on_first_call=True,
... swap_limit=3,
... )
>>> shuffler.pitch_only
False
>>> shuffler.preserve_rest_position
True
>>> shuffler.disable_rewrite_meter
False
>>> shuffler.omit_time_signatures
True
>>> shuffler.boundary_depth
0
>>> shuffler.maximum_dot_count
1
>>> shuffler.rewrite_tuplets
False
>>> shuffler.process_on_first_call
True
>>> shuffler.swap_limit
3
Use the properties below to change these values after initialisation.
>>> shuffler.pitch_only = True
>>> shuffler.preserve_rest_position = False
>>> shuffler.disable_rewrite_meter = True
>>> shuffler.omit_time_signatures = False
>>> shuffler.boundary_depth = 1
>>> shuffler.maximum_dot_count = 2
>>> shuffler.rewrite_tuplets = True
>>> shuffler.process_on_first_call = False
>>> shuffler.swap_limit = None
>>> shuffler.pitch_only
True
>>> shuffler.preserve_rest_position
True
>>> shuffler.disable_rewrite_meter
True
>>> shuffler.omit_time_signatures
False
>>> shuffler.boundary_depth
1
>>> shuffler.maximum_dot_count
2
>>> shuffler.rewrite_tuplets
True
>>> shuffler.process_on_first_call
False
>>> shuffler.swap_limit
None
:attr:`pitch_only`:
By default, the shuffling operation will shuffle logical ties:
>>> container = abjad.Container(r"c'8. d'4 r8 r8. e'16 f'8.")
>>> shuffler = auxjad.Shuffler(container)
>>> notes = shuffler()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
r4
r16
e'16
f'8
~
f'16
d'8.
~
d'16
c'8.
}
.. figure:: ../_images/Shuffler-5j79m0wuxu.png
Setting :attr:`pitch_only` to ``True`` enables pitch mode, so only
pitches are shuffled (and not durations). Note how in the example below
the duration of each leaf is the same as the input container.
>>> container = abjad.Container(r"c'8. d'4 r8 r8. e'16 f'8.")
>>> shuffler = auxjad.Shuffler(container, pitch_only=True)
>>> notes = shuffler()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
f'8.
r4
d'8
~
d'8.
~
c'16
e'8.
}
.. figure:: ../_images/Shuffler-f9jbzqkrkdf.png
.. note::
Altering the value of :attr:`pitch_only`: will replace the original
:attr:`contents`: with the contents of :attr:`current_window`. Note how
in the example below, the shuffled leaves in measure 3 comes from the
previous measure and not from the initial :attr:`contents`:.
>>> container = abjad.Container(r"c'4.. d'16 e'4. f'8")
>>> shuffler = auxjad.Shuffler(container, pitch_only=True)
>>> notes = shuffler.shuffle_n(2)
>>> staff = abjad.Staff(notes)
>>> shuffler.pitch_only = False
>>> notes = shuffler.shuffle_n(2)
>>> staff.append(notes)
>>> auxjad.mutate.remove_repeated_time_signatures(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
d'4..
c'16
f'4.
e'8
d'4..
f'16
c'4.
e'8
f'16
d'4..
e'8
c'4.
c'4.
d'8
~
d'4
~
d'16
e'8
f'16
}
.. figure:: ../_images/Shuffler-tyq8y6q8zr9.png
:attr:`swap_limit`:
The attribute :attr:`swap_limit` can be used to set the number of times
that pairs of elements are swapped on a single invocation of the
shuffling process. Set :attr:`swap_limit` to ``None`` to not limit the
shuffling process.
>>> container = abjad.Container(r"c'8 d'8 e'8 f'8 g'8 a'8 b'8 c''8")
>>> shuffler = auxjad.Shuffler(container,
... swap_limit=1,
... )
>>> notes = shuffler.shuffle_n(3)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
c'8
d'8
e'8
g'8
f'8
a'8
b'8
c''8
c'8
c''8
e'8
g'8
f'8
a'8
b'8
d'8
c'8
c''8
e'8
g'8
b'8
a'8
f'8
d'8
}
.. figure:: ../_images/Shuffler-Nbo5S6wcfQ.png
:meth:`rotate`:
Besides shuffling, logical ties and pitches can also be rotated using
the :meth:`rotate` method. Similarly to shuffling, it can be applied to
logical ties or pitches only depending on the property
:attr:`pitch_only`.
>>> container = abjad.Container(
... r"\time 3/4 c'16 d'8. ~ d'4 e'4 r4 f'4 ~ f'8.. g'32"
... )
>>> shuffler = auxjad.Shuffler(container)
>>> notes = shuffler.rotate()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/4
d'4..
e'16
~
e'8.
r16
r8.
f'16
~
f'4
~
f'8
~
f'32
g'32
c'16
}
.. figure:: ../_images/Shuffler-7vamgsxlr6.png
>>> container = abjad.Container(
... r"\time 3/4 c'16 d'8. ~ d'4 e'4 r4 f'4 ~ f'8.. g'32"
... )
>>> shuffler = auxjad.Shuffler(container, pitch_only=True)
>>> notes = shuffler.rotate()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/4
d'16
e'8.
~
e'4
r4
f'4
g'4
~
g'8..
c'32
}
.. figure:: ../_images/Shuffler-89cx79bjji8.png
This method can also take the optional parameters ``n_rotations`` and
``anticlockwise``. The first is an :obj:`int` setting the number of
rotations applied to the material, and the second is a :obj:`bool`
setting the direction of the rotation (default ``False``).
>>> container = abjad.Container(
... r"\time 3/4 c'16 d'8. ~ d'4 e'4 r4 f'4 ~ f'8.. g'32"
... )
>>> shuffler = auxjad.Shuffler(container, pitch_only=True)
>>> notes = shuffler.rotate(n_rotations=2, anticlockwise=True)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/4
f'16
g'8.
~
g'4
c'4
d'4
e'4
~
e'8..
r32
}
.. figure:: ../_images/Shuffler-g6v6wjm12ub.png
:attr:`preserve_rest_position`:
If :attr:`preserve_rest_position` is set to ``True``, the positions of
all rests will remain the same after either shuffling and rotation. In
pitch mode (when :attr:`pitch_only` is set to ``True``), this means
that only the pitched notes will be shuffled or rotated, while the
rests remain in the exact same place.
>>> container = abjad.Container(r"c'8. d'4 r8 r8. e'16 f'8.")
>>> shuffler = auxjad.Shuffler(container,
... pitch_only=True,
... preserve_rest_position=True,
... )
>>> notes = shuffler()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
d'8.
f'4
r8
r8.
c'16
e'8.
}
.. figure:: ../_images/Shuffler-pmou83f7rlj.png
In logical ties mode, the rests will remain at the same index and will
have the same total duration as before, but their position in the
measure might vary since the duration of the pitched logical ties
preceding it might change.
>>> container = abjad.Container(r"c'8. d'4 r8 r8. e'16 f'8.")
>>> shuffler = auxjad.Shuffler(container, preserve_rest_position=True)
>>> notes = shuffler()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
d'4
e'16
r8.
r8
f'8
~
f'16
c'8.
}
.. figure:: ../_images/Shuffler-7hbp2kdpqof.png
:attr:`disable_rewrite_meter`:
If :attr:`disable_rewrite_meter` is set to ``True``, then the automatic
behaviour of rewriting the leaves according to the meter is disabled.
>>> container = abjad.Container(r"c'4 d'8 e'8 f'2")
>>> shuffler = auxjad.Shuffler(container,
... disable_rewrite_meter=True,
... )
>>> notes = shuffler()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
e'8
f'2
c'4
d'8
}
.. figure:: ../_images/Shuffler-tb78izpzvjp.png
:meth:`shuffle_n` and :meth:`rotate_n`:
To output several shuffled containers at once, use the methods
:meth:`shuffle_n` and :meth:`rotate_n`, inputting the desired number of
iterations. :meth:`rotate_n` can also take the optional arguments
``n_rotations`` and ``anticlockwise``, similarly to :meth:`rotate`.
>>> container = abjad.Container(r"c'4 d'8 e'4. f'8. g'16")
>>> shuffler = auxjad.Shuffler(container)
>>> notes = shuffler.shuffle_n(2)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
d'8
g'16
c'16
~
c'8.
f'16
~
f'8
e'4.
g'16
f'8.
e'4
~
e'8
c'4
d'8
}
.. figure:: ../_images/Shuffler-vtia65lbk5.png
>>> container = abjad.Container(r"c'4 d'8 e'4. f'8. g'16")
>>> shuffler = auxjad.Shuffler(container)
>>> notes = shuffler.rotate_n(2)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
d'8
e'4.
f'8.
g'16
c'4
e'4.
f'8
~
f'16
g'16
c'4
d'8
}
.. figure:: ../_images/Shuffler-3dqhy8eoiez.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 change the :attr:`omit_time_signatures` property
after initialisation.
>>> container = abjad.Container(r"\time 3/4 c'16 d'4.. e'4 | r4 f'2")
>>> shuffler = auxjad.Shuffler(container,
... omit_time_signatures=True,
... )
>>> notes = shuffler()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
d'4..
e'16
~
e'8.
f'16
~
f'4..
r16
r8.
c'16
}
.. figure:: ../_images/Shuffler-1v3lwhj430b.png
.. tip::
All methods that return an |abjad.Selection| will add an initial time
signature to it. The :meth:`shuffle_n` and :meth:`rotate_n` 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.
Time signature changes:
This class handles time signature changes too:
>>> container = abjad.Container(
... r"\time 3/4 c'8. d'4 r8 r8. \time 2/4 e'16 f'4.."
... )
>>> shuffler = auxjad.Shuffler(container)
>>> notes = shuffler.shuffle_n(2)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/4
e'16
d'8.
~
d'16
f'4..
\time 2/4
c'8.
r16
r4
\time 3/4
c'8.
f'16
~
f'4.
r8
\time 2/4
r8.
d'16
~
d'8.
e'16
}
.. figure:: ../_images/Shuffler-yx11u6o14v.png
Tuplet support:
Tuplets are supported when :attr:`pitch_only` is ``True`` (pitch-only
mode).
>>> container = abjad.Container(
... r"\time 5/4 r4 \times 2/3 {c'4 d'2} e'4. f'8"
... )
>>> shuffler = auxjad.Shuffler(container, pitch_only=True)
>>> notes = shuffler()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 5/4
d'4
\times 2/3
{
f'4
c'2
}
e'4.
r8
}
.. figure:: ../_images/Shuffler-mjxubkel8y.png
.. error::
Tuplets are not supported when :attr:`pitch_only` is ``False`` (logical
tie mode). Using a container with tuplets and :attr:`pitch_only` set to
``True`` will raise a :exc:`TypeError` exception.
>>> container = abjad.Container(
... r"\time 5/4 r4 \times 2/3 {c'4 d'2} e'4. f'8"
... )
>>> shuffler = auxjad.Shuffler(container)
>>> notes = shuffler()
TypeError: 'contents' contain one ore more tuplets, which are not
currently supported by the shuffle method
Indicators:
This class can also handle dynamics and articulations.
>>> container = abjad.Container(
... r"<c' e' g'>4--\p d'8-. e'8-. f'4-^\f r4"
... )
>>> shuffler = auxjad.Shuffler(container)
>>> notes = shuffler.shuffle_n(3)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
e'8
\p
- \staccato
d'8
- \staccato
f'4
\f
- \marcato
<c' e' g'>4
\p
- \tenuto
r4
r4
d'8
- \staccato
f'8
\f
- \marcato
~
f'8
<c' e' g'>4
\p
- \tenuto
e'8
- \staccato
f'4
\f
- \marcato
e'8
\p
- \staccato
<c' e' g'>8
- \tenuto
~
<c' e' g'>8
d'8
- \staccato
r4
}
.. figure:: ../_images/Shuffler-2ibui58pj8w.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.
In the case of shuffling logical ties, slurs and hairpins can also
become a problem, since their start and end position can shift around.
Dynamics are shuffled together with their leaves, so the initial leaf
may lack a dynamic marking.
:attr:`contents`:
Use the property :attr:`contents` to get the input container upon which
the shuffler operates. Notice that :attr:`contents` remains invariant
after any shuffling or rotation operations (use :attr:`current_window`
for the transformed selection of music). :attr:`contents` can be used
to change the |abjad.Container| to be shuffled.
>>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
>>> shuffler = auxjad.Shuffler(container)
>>> abjad.show(shuffler.contents)
.. docs::
{
c'4
d'4
e'4
f'4
}
.. figure:: ../_images/Shuffler-qsee7chymo.png
>>> shuffler()
>>> abjad.show(shuffler.contents)
.. docs::
{
c'4
d'4
e'4
f'4
}
.. figure:: ../_images/Shuffler-ii3fxe001ki.png
>>> shuffler.contents = abjad.Container(r"cs2 ds2")
>>> abjad.show(shuffler.contents)
.. docs::
{
cs2
ds2
}
.. figure:: ../_images/Shuffler-p2vd4mfvucp.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")
>>> shuffler = auxjad.Shuffler(container)
>>> notes = shuffler()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
e'2
c'4.
d'8
}
.. figure:: ../_images/Shuffler-t4lsqxg18ab.png
Set :attr:`boundary_depth` to a different number to change its
behaviour.
>>> shuffler = auxjad.Shuffler(container,
... boundary_depth=1,
... )
>>> notes = shuffler()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
e'2
c'4
~
c'8
d'8
}
.. figure:: ../_images/Shuffler-7na5znnhhwe.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:`disable_rewrite_meter`:
By default, this class uses the |abjad.Meter.rewrite_meter()|
mutation.
>>> container = abjad.Container(r"c'4 d'8 e'8 f'2")
>>> shuffler = auxjad.Shuffler(container)
>>> notes = shuffler()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
e'8
f'8
~
f'4.
c'8
~
c'8
d'8
}
.. figure:: ../_images/Shuffler-7cfnxx7shci.png
Set :attr:`disable_rewrite_meter` to ``True`` in order to disable this
behaviour.
>>> container = abjad.Container(r"c'4 d'8. e'16 f'2")
>>> abjad.show(container)
.. docs::
{
e'16
f'8.
~
f'4
~
f'16
c'8.
~
c'16
d'8.
}
.. figure:: ../_images/Shuffler-6gm4ev48j9k.png
>>> shuffler = auxjad.Shuffler(container,
... disable_rewrite_meter=True,
... )
>>> notes = shuffler()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 4/4
e'16
f'2
c'4
d'8.
}
.. figure:: ../_images/Shuffler-xlr4x3bhj6n.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. Note that unlike the methods
:meth:`shuffle_n` and :meth:`rotate_n`, 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 this class in this way. It is also important to note that a
``break`` statement is needed when using this class as an iterator. The
reason is that shuffling is a process that can happen indefinitely
(unlike some of the other classes in this library).
>>> container = abjad.Container(r"\time 3/4 c'4 d'4 e'4")
>>> shuffler = auxjad.Shuffler(container)
>>> staff = abjad.Staff()
>>> for window in shuffler:
... staff.append(window)
... if abjad.get.duration(staff) == abjad.Duration((9, 4)):
... break
>>> auxjad.mutate.remove_repeated_time_signatures(staff[:])
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/4
e'4
c'4
d'4
d'4
c'4
e'4
c'4
e'4
d'4
}
.. figure:: ../_images/Shuffler-3gyz7atvemx.png
"""
### CLASS VARIABLES ###
__slots__ = ('_contents',
'_pitch_only',
'_preserve_rest_position',
'_disable_rewrite_meter',
'_omit_time_signatures',
'_current_window',
'_logical_selections',
'_logical_selections_indeces',
'_pitches',
'_time_signatures',
'_is_first_window',
'_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',
'_swap_limit',
)
### INITIALISER ###
[docs] def __init__(self,
contents: abjad.Container,
*,
pitch_only: bool = False,
preserve_rest_position: bool = False,
disable_rewrite_meter: bool = False,
omit_time_signatures: bool = False,
boundary_depth: Optional[int] = None,
maximum_dot_count: Optional[int] = None,
rewrite_tuplets: bool = True,
process_on_first_call: 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,
swap_limit: Optional[int] = None,
) -> None:
r'Initialises self.'
self.contents = contents
self.pitch_only = pitch_only
self.preserve_rest_position = preserve_rest_position
self.disable_rewrite_meter = disable_rewrite_meter
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.swap_limit = swap_limit
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`.'
return len(self._logical_selections)
[docs] def __call__(self) -> abjad.Selection:
r'Calls the shuffling process, returning an |abjad.Selection|'
return self.shuffle()
[docs] def __next__(self) -> abjad.Selection:
r"""Calls the shuffling process for one iteration, returning an
|abjad.Selection|.
"""
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 shuffle(self) -> abjad.Selection:
r'Shuffles logical ties or pitches of :attr:`contents`.'
if self._is_first_window and not self._process_on_first_call:
if not self._pitch_only:
self._rewrite_logical_selections()
return self.current_window
else:
self._rewrite_pitches()
return self.current_window
else:
if not self._pitch_only:
return self._shuffle_logical_selections()
else:
return self._shuffle_pitches()
[docs] def rotate(self,
*,
n_rotations: int = 1,
anticlockwise: bool = False,
) -> abjad.Selection:
r'Rotates logical ties or pitches of :attr:`contents`.'
if not isinstance(n_rotations, int):
raise TypeError("'n_rotations' must be 'int'")
if n_rotations < 1:
raise ValueError("'n_rotations' must be greater than zero")
if not isinstance(anticlockwise, bool):
raise TypeError("'anticlockwise' must be 'bool'")
if self._is_first_window and not self._process_on_first_call:
if not self._pitch_only:
self._rewrite_logical_selections()
return self.current_window
else:
self._rewrite_pitches()
return self.current_window
else:
if not self._pitch_only:
return self._rotate_logical_selections(
n_rotations=n_rotations,
anticlockwise=anticlockwise,
)
else:
return self._rotate_pitches(
n_rotations=n_rotations,
anticlockwise=anticlockwise,
)
[docs] def shuffle_n(self,
n: int,
) -> abjad.Selection:
r"""Goes through ``n`` iterations of the shuffling process and outputs
a single |abjad.Selection|.
"""
if not isinstance(n, int):
raise TypeError("argument must be 'int'")
if n < 1:
raise ValueError("argument must be greater than zero")
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 rotate_n(self,
n: int,
*,
n_rotations: int = 1,
anticlockwise: bool = False,
) -> abjad.Selection:
r"""Goes through ``n`` iterations of the pitch shuffling process and
outputs a single |abjad.Selection|.
"""
if not isinstance(n, int):
raise TypeError("argument must be 'int'")
if n < 1:
raise ValueError("argument must be greater than zero")
dummy_container = abjad.Container()
for _ in range(n):
dummy_container.append(self.rotate(n_rotations=n_rotations,
anticlockwise=anticlockwise))
mutate.remove_repeated_time_signatures(dummy_container[:])
mutate.remove_repeated_dynamics(dummy_container[:])
output = dummy_container[:]
dummy_container[:] = []
return output
### PRIVATE METHODS ###
def _update_logical_selections(self) -> None:
r'Updates the selection of logical ties of :attr:`contents`.'
self._logical_selections = select.logical_selections(self._contents)
self._logical_selections_indeces = list(range(self.__len__()))
def _get_pitch_list(self) -> None:
r'Creates a :obj:`list` of all pitches in :attr:`contents`.'
self._pitches = []
for logical_selection in self._logical_selections:
leaf = logical_selection.leaves()[0]
if isinstance(leaf, abjad.Rest):
self._pitches.append(None)
elif isinstance(leaf, abjad.Note):
self._pitches.append(leaf.written_pitch)
elif isinstance(leaf, abjad.Chord):
self._pitches.append(leaf.written_pitches)
def _shuffle_list_preserving_rests(self,
input_list: list[Any],
) -> None:
r'Shuffles a :obj:`list` while keeping rest indeces unchanged.'
dummy_list = [input_list[i] for i in range(len(input_list))
if self._pitches[i] is not None]
self._random_shuffle(dummy_list)
self._replace_list_preserving_rests(dummy_list, input_list)
def _rotate_list_preserving_rests(self,
input_list: list[Any],
*,
n_rotations: int = 1,
anticlockwise: bool = False,
) -> None:
r'Rotates a :obj:`list` while keeping rest indeces unchanged.'
dummy_list = [input_list[i] for i in range(len(input_list))
if self._pitches[i] is not None]
self._rotate_list(dummy_list,
n_rotations=n_rotations,
anticlockwise=anticlockwise,
)
self._replace_list_preserving_rests(dummy_list, input_list)
def _random_shuffle(self,
input_list: list[Any],
) -> None:
r'Random shuffles a :obj:`list`.'
if self._swap_limit is None:
random.shuffle(input_list)
else:
for _ in range(self._swap_limit):
if len(input_list) > 1:
i, j = random.sample(range(len(input_list)), 2)
input_list[i], input_list[j] = input_list[j], input_list[i]
def _replace_list_preserving_rests(self,
input_list: list[Any],
destination_list: list[Any],
) -> None:
r'Substitutes back an altered :obj:`list` while preserving rests.'
counter = 0
for index, pitch in enumerate(self._pitches):
if pitch is not None:
destination_list[index] = input_list[counter]
counter += 1
def _shuffle_logical_selections(self) -> abjad.Selection:
r'Shuffles the logical ties of :attr:`contents`.'
if len(abjad.select(self._contents).tuplets()) > 0:
raise ValueError("'contents' contain one ore more tuplets; "
"tuplets are currently supported only in "
"pitch-only mode")
if not self._preserve_rest_position:
self._random_shuffle(self._logical_selections_indeces)
else:
self._shuffle_list_preserving_rests(
self._logical_selections_indeces
)
self._rewrite_logical_selections()
return self.current_window
def _shuffle_pitches(self) -> abjad.Selection:
r'Shuffles only the pitches of :attr:`contents`.'
if not self._preserve_rest_position:
self._random_shuffle(self._pitches)
else:
self._shuffle_list_preserving_rests(self._pitches)
self._rewrite_pitches()
return self.current_window
def _rotate_logical_selections(self,
*,
n_rotations: int = 1,
anticlockwise: bool = False,
) -> abjad.Selection:
r'Rotates the logical ties of :attr:`contents`.'
if len(abjad.select(self._contents).tuplets()) > 0:
raise ValueError("'contents' contain one ore more tuplets; "
"tuplets are currently supported only in "
"pitch-only mode")
if not self._preserve_rest_position:
self._rotate_list(self._logical_selections_indeces,
n_rotations=n_rotations,
anticlockwise=anticlockwise,
)
else:
self._rotate_list_preserving_rests(
self._logical_selections_indeces,
n_rotations=n_rotations,
anticlockwise=anticlockwise,
)
self._rewrite_logical_selections()
return self.current_window
def _rotate_pitches(self,
*,
n_rotations: int = 1,
anticlockwise: bool = False,
) -> abjad.Selection:
r'Rotates the pitches of :attr:`contents`.'
if not self._preserve_rest_position:
self._rotate_list(self._pitches,
n_rotations=n_rotations,
anticlockwise=anticlockwise,
)
else:
self._rotate_list_preserving_rests(self._pitches,
n_rotations=n_rotations,
anticlockwise=anticlockwise,
)
self._rewrite_pitches()
return self.current_window
def _rewrite_logical_selections(self) -> None:
r'Rewrites the logical selections of the current window.'
# writing dummy_container in shuffled order
dummy_container = abjad.Container()
logical_selections = select.logical_selections(
abjad.mutate.copy(self._contents)
)
self._force_dynamics(logical_selections)
for index in self._logical_selections_indeces:
logical_selection = logical_selections[index]
dummy_container.append(logical_selection.leaves())
# splitting leaves at measure line points
abjad.mutate.split(dummy_container[:],
[ts.duration for ts in self._time_signatures],
cyclic=True,
)
# attaching time signature structure
mutate.enforce_time_signature(
dummy_container,
self._time_signatures,
disable_rewrite_meter=True,
)
# handling dynamics and slurs
mutate.reposition_dynamics(dummy_container[:])
mutate.reposition_slurs(dummy_container[:])
# rewrite meter
if not self._disable_rewrite_meter:
mutate.auto_rewrite_meter(
dummy_container,
meter_list=self._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,
)
# output
self._is_first_window = False
self._current_window = dummy_container[:]
dummy_container[:] = []
def _rewrite_pitches(self) -> None:
r'Rewrites the pitches of the current window.'
dummy_container = abjad.Container(abjad.mutate.copy(self._contents[:]))
leaf_counter = 0
for pitch, logical_selection in zip(self._pitches,
self._logical_selections,
):
logical_tie = logical_selection.leaves()
for leaf in logical_tie:
if pitch is None:
new_leaf = abjad.Rest(leaf.written_duration)
elif isinstance(pitch, abjad.PitchSegment):
new_leaf = abjad.Chord(pitch, leaf.written_duration)
if (isinstance(leaf, abjad.Rest) and len(logical_tie) > 1
and leaf is not logical_tie[-1]):
abjad.attach(abjad.Tie(), new_leaf)
else:
new_leaf = abjad.Note(pitch, leaf.written_duration)
if (isinstance(leaf, abjad.Rest) and len(logical_tie) > 1
and leaf is not logical_tie[-1]):
abjad.attach(abjad.Tie(), new_leaf)
for indicator in abjad.get.indicators(leaf):
if (isinstance(indicator, (abjad.Tie, abjad.Articulation))
and pitch is None):
continue
if isinstance(indicator, abjad.TimeSignature):
abjad.attach(indicator, new_leaf)
else:
abjad.attach(indicator, new_leaf)
selection = abjad.select(dummy_container).leaf(leaf_counter)
abjad.mutate.replace(selection, new_leaf)
leaf_counter += 1
# attaching time signature structure
mutate.extract_trivial_tuplets(dummy_container[:])
mutate.enforce_time_signature(
dummy_container,
self._time_signatures,
disable_rewrite_meter=True,
)
# output
self._is_first_window = False
self._current_window = dummy_container[:]
dummy_container[:] = []
def _get_lilypond_format(self) -> str:
r'Returns interpreter representation of :attr:`contents`.'
return self.__repr__()
@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)
@staticmethod
def _force_dynamics(container) -> None:
logical_ties = abjad.select(container).logical_ties()
for logical_tie in logical_ties[1:]:
if abjad.get.indicator(logical_tie[0], abjad.Dynamic) is None:
index = logical_ties.index(logical_tie)
previous_logical_tie = logical_ties[index - 1]
if (abjad.get.indicator(previous_logical_tie[0], abjad.Dynamic)
is not None):
abjad.attach(abjad.get.indicator(previous_logical_tie[0],
abjad.Dynamic,
),
logical_tie[0],
)
@staticmethod
def _rotate_list(input_list: list[Any],
*,
n_rotations: int = 1,
anticlockwise: bool = False,
) -> None:
r'Rotates a :obj:`list`.'
for _ in range(n_rotations):
if not anticlockwise:
element = input_list.pop(0)
input_list.append(element)
else:
element = input_list.pop(-1)
input_list.insert(0, element)
### PUBLIC PROPERTIES ###
@property
def contents(self) -> abjad.Container:
r'The |abjad.Container| to be shuffled.'
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._update_logical_selections()
self._get_pitch_list()
self._time_signatures = get.time_signature_list(
self._contents,
do_not_use_none=True,
)
self._is_first_window = True
@property
def pitch_only(self) -> bool:
r"""When ``True``, only the pitches will be shuffled or rotated while
the durations remain the same.
"""
return self._pitch_only
@pitch_only.setter
def pitch_only(self,
pitch_only: bool,
) -> None:
if not isinstance(pitch_only, bool):
raise TypeError("'pitch_only' must be 'bool'")
self._pitch_only = pitch_only
# potentially new logical selections when shifting from pitch-only mode
# to logical selections mode
self._update_logical_selections()
self._get_pitch_list()
self._contents = abjad.Container(
abjad.mutate.copy(self._current_window)
)
@property
def preserve_rest_position(self) -> bool:
r"""When ``True``, shuffle operations will preserve rest positions and
durations.
"""
return self._preserve_rest_position
@preserve_rest_position.setter
def preserve_rest_position(self,
preserve_rest_position: bool,
) -> None:
if not isinstance(preserve_rest_position, bool):
raise TypeError("'preserve_rest_position' must be 'bool'")
self._preserve_rest_position = preserve_rest_position
@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``, the output will contain no time signatures.'
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 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 swap_limit(self) -> int:
r"""If :obj:`int` then it dictates how instances of random swapping of
pairs of elements are applied to :attr:`contents` in each iteration. If
``None`` then :attr:`contents` is completely shuffled at each
iteration.
"""
return self._swap_limit
@swap_limit.setter
def swap_limit(self,
swap_limit: Optional[int],
) -> None:
if swap_limit is not None:
if not isinstance(swap_limit, int):
raise TypeError("'swap_limit' must be 'int'")
if swap_limit < 1:
raise ValueError("'swap_limit' must be equal to or greater "
"than 1")
self._swap_limit = swap_limit
@property
def current_window(self) -> abjad.Selection:
r'Read-only property, returns the result of the last operation.'
current_window = abjad.mutate.copy(self._current_window)
if self._omit_time_signatures:
self._remove_all_time_signatures(current_window)
return current_window