Source code for auxjad.core.Shuffler

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