import copy
import random
from typing import Any, Optional, Union

import abjad

from ..utilities.enforce_time_signature import enforce_time_signature
from ..utilities.remove_repeated_time_signatures import (
    remove_repeated_time_signatures,
)
from ..utilities.rests_to_multimeasure_rest import rests_to_multimeasure_rest
from ..utilities.time_signature_extractor import time_signature_extractor


class Fader():
    r"""``Fader`` takes an ``abjad.Container`` (or child class) as input and
    gradually removes or adds its logical ties one by one.

    Example:
        Calling the object will return an ``abjad.Selection`` generated by
        the fading process. Each call of the object will apply the fading
        process to the previous result. By default, the container will be faded
        out (that is, its logical ties will be gradually removed one by one).

        >>> container = abjad.Container(r"c'4 ~ c'16 d'8. e'8 f'8 ~ f'4")
        >>> fader = auxjad.Fader(container)
        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'4
            ~
            c'16
            d'8.
            e'8
            f'4.
        }

        .. figure:: ../_images/image-Fader-1.png

        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'4
            ~
            c'16
            r8.
            e'8
            f'4.
        }

        .. figure:: ../_images/image-Fader-2.png

        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            r2
            e'8
            f'4.
        }

        .. figure:: ../_images/image-Fader-3.png

        The property ``current_window`` can be used to access the current
        window without moving the head forwards.

        >>> notes = fader.current_window()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            r2
            e'8
            f'4.
        }

        .. figure:: ../_images/image-Fader-4.png

    Example:
        The very first call will output the input container without processing
        it. To disable this behaviour and apply the fading process on the very
        first call, initialise the class with the keyword argument
        ``processs_on_first_call`` set to ``True``.

        >>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
        >>> fader = auxjad.Fader(container,
        ...                      processs_on_first_call=True,
        ...                      )
        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'4
            d'4
            e'4
            r4
        }

        .. figure:: ../_images/image-Fader-5.png

    Example:
        The fader can be of two types, either ``'in'`` or ``'out'`` defined by
        the keyword argument ``fader_type``. When it is set to ``'in'``, the
        fader will start with an empty container with the same length and time
        signature structure as the input music and will gradually add the
        original logical ties one by one.

        >>> container = abjad.Container(r"c'4 ~ c'16 d'8. e'8 f'8 ~ f'4")
        >>> fader = auxjad.Fader(container,
        ...                      fader_type='in',
        ...                      )
        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            R1
        }

        .. figure:: ../_images/image-Fader-6.png

        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            r2
            r8
            f'4.
        }

        .. figure:: ../_images/image-Fader-7.png

        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'4
            ~
            c'16
            r8.
            r8
            f'4.
        }

        .. figure:: ../_images/image-Fader-8.png

    Example:
        The property ``fader_type`` can also be changed after initialisation,
        as shown below.

        >>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
        >>> fader = auxjad.Fader(container)
        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'4
            d'4
            e'4
            f'4
        }

        .. figure:: ../_images/image-Fader-9.png

        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'4
            d'4
            r4
            f'4
        }

        .. figure:: ../_images/image-Fader-10.png

        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'4
            d'4
            r2
        }

        .. figure:: ../_images/image-Fader-11.png

        >>> fader.fader_type = 'in'
        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'4
            d'4
            e'4
            r4
        }

        .. figure:: ../_images/image-Fader-12.png

        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'4
            d'4
            e'4
            f'4
        }

        .. figure:: ../_images/image-Fader-13.png

    Example:
        The instances of ``Fader`` can also be used as an iterator, which can
        then be used in a for loop to run through the whole process. Note that
        unlike the methods ``output_n()`` and ``output_all()``, time signatures
        are added to each window returned by the fader. Use the function
        ``auxjad.remove_repeated_time_signatures()`` to clean the output when
        using ``Fader`` in this way.

        >>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
        >>> fader = auxjad.Fader(container)
        >>> staff = abjad.Staff()
        >>> for window in fader:
        ...     staff.append(window)
        >>> auxjad.remove_repeated_time_signatures(staff)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'4
            d'4
            e'4
            f'4
            c'4
            r4
            e'4
            f'4
            c'4
            r4
            e'4
            r4
            c'4
            r2.
            R1
        }

        .. figure:: ../_images/image-Fader-14.png

    Example:
        This class can take many optional keyword arguments during its
        creation, besides ``fader_type``. ``max_steps`` sets the maximum number
        of logical ties that can be faded in/out at each iteration, ranging
        between ``1`` and the input value (default is also ``1``). By default,
        calling the object in fade out mode will return the original container,
        and calling it in fade in mode will return a container filled with
        rests; set ``processs_on_first_call`` to ``True`` and the fade process
        will be applied on the very first call.  ``disable_rewrite_meter``
        disables the ``rewrite_meter()`` mutation which is applied to the
        container after every call, and ``omit_time_signatures`` will remove
        all time signatures from the output (both are ``False`` by default). By
        default, the first time signature is attached only to the first leaf of
        the first call (unless time signature changes require it). Any measure
        filled with rests will be rewritten using a multi-measure rest; set the
        ``use_multimeasure_rests`` to ``False`` to disable this behaviour.
        An initial mask for the logical ties can be set using ``mask``, which
        should be a ``list`` of the same length as the number of pitched
        logical ties in the input container. When ``fader_type`` is set to
        ``'out'``, the mask is initialised with ``1``'s, and when it is set to
        ``'in'``, it is initialised with ``0``'s. Change it to a mix of ``1``'s
        and ``0``'s to start the process with some specific logical ties
        already hidden/shown. The properties ``boundary_depth``,
        ``maximum_dot_count``, and ``rewrite_tuplets`` are passed as arguments
        to abjad's ``rewrite_meter()``, see its documentation for more
        information.

        >>> container = abjad.Container(r"c'4 d'2 e'4 f'2 ~ f'8 g'1")
        >>> fader = auxjad.Fader(container,
        ...                      fader_type='in',
        ...                      max_steps=2,
        ...                      disable_rewrite_meter=True,
        ...                      omit_time_signatures=True,
        ...                      use_multimeasure_rests=False,
        ...                      mask=[1, 0, 1, 1, 0],
        ...                      boundary_depth=0,
        ...                      maximum_dot_count=1,
        ...                      rewrite_tuplets=False,
        ...                      processs_on_first_call=True,
        ...                      )
        >>> fader.fader_type
        'in'
        >>> fader.max_steps
        2
        >>> fader.disable_rewrite_meter
        True
        >>> fader.omit_time_signatures
        True
        >>> fader.use_multimeasure_rests
        False
        >>> fader.mask
        [1, 0, 1, 1, 0]
        >>> fader.boundary_depth
        0
        >>> fader.maximum_dot_count
        1
        >>> fader.rewrite_tuplets
        False
        >>> fader.processs_on_first_call
        True

        Use the properties below to change these values after initialisation.

        >>> fader.fader_type = 'out'
        >>> fader.max_steps = 1
        >>> fader.disable_rewrite_meter = False
        >>> fader.omit_time_signatures = False
        >>> fader.use_multimeasure_rests = True
        >>> fader.mask = [0, 1, 1, 0, 1]
        >>> fader.boundary_depth = 1
        >>> fader.maximum_dot_count = 2
        >>> fader.rewrite_tuplets = True
        >>> fader.processs_on_first_call = False
        >>> fader.fader_type
        'out'
        >>> fader.max_steps
        1
        >>> fader.disable_rewrite_meter
        False
        >>> fader.omit_time_signatures
        False
        >>> fader.use_multimeasure_rests
        True
        >>> fader.mask
        [0, 1, 1, 0, 1]
        >>> fader.boundary_depth
        1
        >>> fader.maximum_dot_count
        2
        >>> fader.rewrite_tuplets
        True
        >>> fader.processs_on_first_call
        False

    Example:
        Use the ``contents`` property to read as well as overwrite the contents
        of the fader. Notice that ``mask`` will also be reset at that point.

        >>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
        >>> fader = auxjad.Fader(container)
        >>> notes = fader()
        >>> fader.mask
        [1, 1, 1, 1]
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'4
            d'4
            e'4
            f'4
        }

        .. figure:: ../_images/image-Fader-15.png

        >>> notes = fader()
        >>> fader.mask
        [0, 1, 1, 1]
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            r4
            d'4
            e'4
            f'4
        }

        .. figure:: ../_images/image-Fader-16.png

        >>> fader.contents = abjad.Container(r"c'16 d'16 e'16 f'16 g'2.")
        >>> fader.mask
        [1, 1, 1, 1, 1]
        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'16
            d'16
            e'16
            f'16
            g'2.
        }

        .. figure:: ../_images/image-Fader-17.png

        >>> notes = fader()
        >>> fader.mask
        [1, 1, 1, 1, 1]
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'16
            d'16
            r16
            f'16
            g'2.
        }

        .. figure:: ../_images/image-Fader-18.png

    Example:
        To run through the whole process and output it as a single container,
        use the method ``output_all()``.

        >>> container = abjad.Container(r"c'4. d'8 e'2")
        >>> fader = auxjad.Fader(container)
        >>> notes = fader.output_all()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'4.
            d'8
            e'2
            r4.
            d'8
            e'2
            r2
            e'2
            R1
        }

        .. figure:: ../_images/image-Fader-19.png

    Example:
        To run through just part of the process and output it as a single
        container, use the method``output_n()`` and pass the number of
        iterations as argument.

        >>> container = abjad.Container(r"c'4. d'8 e'16 f'16 g'4.")
        >>> fader = auxjad.Fader(container)
        >>> notes = fader.output_n(3)
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'4.
            d'8
            e'16
            f'16
            g'4.
            c'4.
            r8
            e'16
            f'16
            g'4.
            c'4.
            r8
            e'16
            f'16
            r4.
        }

        .. figure:: ../_images/image-Fader-20.png

    Example:
        The function ``len()`` returns the number of pitched logical ties in
        ``contents``.

        >>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
        >>> fader = auxjad.Fader(container)
        >>> len(fader)
        4
        >>> container = abjad.Container(r"<c' e' g'>4 d'4 <e' g' b'>4 f'4")
        >>> fader = auxjad.Fader(container)
        >>> len(fader)
        4
        >>> container = abjad.Container(r"c'4 ~ c'8 d'8 e'4 ~ e'8 f'8")
        >>> fader = auxjad.Fader(container)
        >>> len(fader)
        4
        >>> container = abjad.Container(
        ...     r"c'4 ~ c'16 r16 d'8 e'4 ~ e'8 f'16 r16")
        >>> fader = auxjad.Fader(container)
        >>> len(fader)
        4

    Example:
        Setting the keyword argument ``max_steps`` to a value larger than ``1``
        will result in a random number of steps (between ``1`` and
        ``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")
        >>> fader = auxjad.Fader(container,
        ...                      max_steps=3,
        ...                      processs_on_first_call=True,
        ...                      )
        >>> notes = fader.output_n(3)
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'8
            d'8
            r8
            f'8
            g'8
            a'8
            b'8
            c''8
            r4.
            f'8
            g'8
            a'8
            b'8
            c''8
            r4.
            f'8
            r8
            a'8
            b'8
            r8
        }

        .. figure:: ../_images/image-Fader-21.png

    Example:
        The property ``mask`` is used to represent whether each pitched logical
        tie is hidden or shown. It is a ``list`` of the same length as the
        number of pitched logical ties in the input container. When
        ``fader_type`` is set to ``'out'``, the mask is initialised with
        ``1``'s, and when it is set to ``'in'``, it is initialised with
        ``0``'s. Change it to a mix of ``1``'s and ``0``'s to start the process
        with some logical ties already hidden/shown. Use the method
        ``reset_mask()`` to reset it back to its default value (depending on
        ``fader_type``).

        >>> container = abjad.Container(r"c'4 d'8 e'8 f'4 ~ f'8. g'16")
        >>> fader = auxjad.Fader(container)
        >>> fader.mask
        [1, 1, 1, 1, 1]
        >>> fader = auxjad.Fader(container,
        ...                      fader_type='in',
        ...                      )
        >>> fader.mask
        [0, 0, 0, 0, 0]
        >>> for _ in range(3):
        ...     fader()
        ...     fader.mask
        [0, 0, 0, 0, 0]
        [0, 1, 0, 0, 0]
        [0, 1, 1, 0, 0]
        >>> staff = abjad.Staff(fader.current_window)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            r4
            d'8
            e'8
            r2
        }

        .. figure:: ../_images/image-Fader-22.png

        >>> fader.mask = [1, 0, 1, 1, 0]
        >>> fader.mask
        [1, 0, 1, 1, 0]
        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'4
            r8
            e'8
            f'4..
            r16
        }

        .. figure:: ../_images/image-Fader-23.png

        >>> fader.reset_mask()
        >>> fader.mask
        [0, 0, 0, 0, 0]
        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            R1
        }

        .. figure:: ../_images/image-Fader-24.png

    Example:
        The mask can also be randomised at any point using the method
        ``random_mask()``.

        >>> container = abjad.Container(r"c'8 d'8 e'8 f'8 g'8 a'8 b'8 c''8")
        >>> fader = auxjad.Fader(container)
        >>> fader.random_mask()
        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            r8
            d'8
            r4
            g'8
            a'8
            r4
        }

        .. figure:: ../_images/image-Fader-25.png

        >>> fader.random_mask()
        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            r8
            d'8
            r4
            g'8
            a'8
            b'8
            r8
        }

        .. figure:: ../_images/image-Fader-26.png

    Example:
        Use ``shuffle_mask()`` to shuffle the current mask. This method will
        shuffle the mask while keeping the same number of pitched logical ties
        (that is, it will shuffle the contents of the mask while keeping the
        total number of ``1``'s and ``0``'s).

        >>> container = abjad.Container(r"c'8 d'8 e'8 f'8 g'8 a'8 b'8 c''8")
        >>> fader = auxjad.Fader(container,
        ...                      mask=[0, 0, 1, 1, 1, 1, 1, 1],
        ...                      )
        >>> fader.shuffle_mask()
        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            r8
            d'8
            e'8
            f'8
            g'8
            a'8
            b'8
            r8
        }

        .. figure:: ../_images/image-Fader-27.png

        >>> fader.shuffle_mask()
        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'8
            d'8
            e'8
            r8
            g'8
            r8
            b'8
            c''8
        }

        .. figure:: ../_images/image-Fader-28.png

    Example:
        By default, all rests in a measure filled only with rests will be
        converted into a multi-measure rest. Set ``use_multimeasure_rests`` to
        ``False`` to disable this. Also, by default, all output is mutated
        through abjad's ``rewrite_meter()``. To disable it, set
        ``disable_rewrite_meter`` to ``True``.

        >>> container = abjad.Container(r"c'8 d'8 e'2.")
        >>> fader = auxjad.Fader(container,
        ...                      disable_rewrite_meter=True,
        ...                      use_multimeasure_rests=False,
        ...                      )
        >>> notes = fader.output_all()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'8
            d'8
            e'2.
            c'8
            r8
            e'2.
            r8
            r8
            e'2.
            r8
            r8
            r2.
        }

        .. figure:: ../_images/image-Fader-29.png

    Example:
        To disable time signatures altogether, initialise this class with the
        keyword argument ``omit_time_signatures`` set to ``True`` (default is
        ``False``), or use the ``omit_time_signatures`` property after
        initialisation.

        >>> container = abjad.Container(r"c'4 d'2 e'4 f'2 ~ f'8 g'1")
        >>> fader = auxjad.Fader(container,
        ...                      omit_time_signatures=True,
        ...                      )
        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            c'4
            d'2
            e'4
        }

        .. figure:: ../_images/image-Fader-30.png

    ..  tip::

        All methods that return an ``abjad.Selection`` will add an initial time
        signature to it. The ``output_n()`` and ``output_all()`` methods
        automatically remove repeated time signatures. When joining selections
        output by multiple method calls, use
        ``auxjad.remove_repeated_time_signatures()`` on the whole container
        after fusing the selections to remove any unecessary time signature
        changes.

    Example:
        This function uses the default logical tie splitting algorithm from
        abjad's ``rewrite_meter()``.

        >>> staff = abjad.Staff(r"c'4. d'8 e'2")
        >>> fader = auxjad.Fader(container)
        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'4.
            d'8
            e'2
        }

        .. figure:: ../_images/image-Fader-31.png

        Set ``boundary_depth`` to a different number to change its behaviour.

        >>> fader = auxjad.Fader(container,
        ...                      boundary_depth=1,
        ...                      )
        >>> notes = fader()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 4/4
            c'4
            ~
            c'8
            d'8
            e'2
        }

        .. figure:: ../_images/image-Fader-32.png

        Other arguments available for tweaking the output of abjad's
        ``rewrite_meter()`` are ``maximum_dot_count`` and ``rewrite_tuplets``,
        which work exactly as the identically named arguments of
        ``rewrite_meter()``.

    Example:
        This class can handle dynamics and articulations too. Hairpins might
        need manual tweaking if the leaf under which they terminate is removed.

        >>> container = abjad.Container(
        ...     r"\time 3/4 <e' g' b'>8->\f d'8\p ~ d'4 f'8..-- g'32-.")
        >>> fader = auxjad.Fader(container)
        >>> notes = fader.output_all()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \time 3/4
            <e' g' b'>8
            \f
            - \accent
            d'4.
            \p
            f'8..
            - \tenuto
            g'32
            - \staccato
            <e' g' b'>8
            \f
            - \accent
            d'4.
            \p
            r8..
            g'32
            - \staccato
            <e' g' b'>8
            \f
            - \accent
            d'4.
            \p
            r4
            <e' g' b'>8
            \f
            - \accent
            r8
            r2
            R1 * 3/4
        }

        .. figure:: ../_images/image-Fader-33.png

    .. tip::

        The functions ``auxjad.remove_repeated_dynamics()`` and
        ``auxjad.reposition_clefs()`` can be used to clean the output and
        remove repeated dynamics and unnecessary clef changes.

    ..  warning::

        Do note that elements that span multiple notes (such as hairpins,
        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.

    Example:
        This class can handle tuplets.

        >>> container = abjad.Container(r"\times 2/3 {c'8 d'8 e'8} d'2.")
        >>> fader = auxjad.Fader(container)
        >>> notes = fader.output_all()
        >>> staff = abjad.Staff(notes)
        >>> abjad.f(staff)
        \new Staff
        {
            \times 2/3 {
                \time 4/4
                c'8
                d'8
                e'8
            }
            d'2.
            \times 2/3 {
                r8
                d'8
                e'8
            }
            d'2.
            \times 2/3 {
                r8
                d'8
                r8
            }
            d'2.
            r4
            d'2.
            R1
        }

        .. figure:: ../_images/image-Fader-34.png
    """

    ### CLASS VARIABLES ###

    __slots__ = ('_contents',
                 '_current_window',
                 '_fader_type',
                 '_max_steps',
                 '_disable_rewrite_meter',
                 '_mask',
                 '_is_first_window',
                 '_time_signatures',
                 '_omit_time_signatures',
                 '_use_multimeasure_rests',
                 '_boundary_depth',
                 '_maximum_dot_count',
                 '_rewrite_tuplets',
                 '_processs_on_first_call',
                 )

    ### INITIALISER ###

    def __init__(self,
                 contents: abjad.Container,
                 *,
                 fader_type: str = 'out',
                 max_steps: int = 1,
                 processs_on_first_call: bool = False,
                 disable_rewrite_meter: bool = False,
                 omit_time_signatures: bool = False,
                 use_multimeasure_rests: bool = True,
                 mask: Optional[list] = None,
                 boundary_depth: Optional[int] = None,
                 maximum_dot_count: Optional[int] = None,
                 rewrite_tuplets: bool = True,
                 ):
        r'Initialises self.'
        self.fader_type = fader_type
        self.max_steps = max_steps
        self.contents = contents
        self.disable_rewrite_meter = disable_rewrite_meter
        self.omit_time_signatures = omit_time_signatures
        self.use_multimeasure_rests = use_multimeasure_rests
        if mask:
            self.mask = mask
        self.boundary_depth = boundary_depth
        self.maximum_dot_count = maximum_dot_count
        self.rewrite_tuplets = rewrite_tuplets
        self.processs_on_first_call = processs_on_first_call
        self._is_first_window = True

    ### SPECIAL METHODS ###

    def __repr__(self) -> str:
        r'Returns interpret representation of  ``contents``.'
        return format(self._contents)

    def __len__(self) -> int:
        r'Returns the number of pitched logical ties ``contents``.'
        return len(abjad.select(self._contents).logical_ties(pitched=True))

    def __call__(self) -> abjad.Selection:
        r"""Calls the fading process for one iteration, returning an
        ``abjad.Selection``.
        """
        if not self._is_first_window or self._processs_on_first_call:
            if self._fader_type == 'out':
                self._remove_element()
            else:
                self._add_element()
        self._mask_to_selection()
        return self.current_window

    def __next__(self) -> abjad.Selection:
        r"""Calls the fading process for one iteration, returning an
        ``abjad.Selection``.
        """
        if self._done:
            raise StopIteration
        return self.__call__()

    def __iter__(self):
        r'Returns an iterator, allowing instances to be used as iterators.'
        return self

    ### PUBLIC METHODS ###

    def output_all(self) -> abjad.Selection:
        r"""Goes through the whole fading process and outputs a single
        ``abjad.Selection``.
        """
        dummy_container = abjad.Container()
        while True:
            dummy_container.append(self.__call__())
            if self._done:
                break
        remove_repeated_time_signatures(dummy_container)
        output = dummy_container[:]
        dummy_container[:] = []
        return output

    def output_n(self,
                 n: int,
                 ) -> abjad.Selection:
        r"""Goes through ``n`` iterations of the fading process and outputs a
        single ``abjad.Selection``.
        """
        if not isinstance(n, int):
            raise TypeError("first positional argument must be 'int'")
        if n < 1:
            raise ValueError("first positional argument must be a positive "
                             "'int'")
        dummy_container = abjad.Container()
        for _ in range(n):
            dummy_container.append(self.__call__())
        remove_repeated_time_signatures(dummy_container)
        output = dummy_container[:]
        dummy_container[:] = []
        return output

    def reset_mask(self):
        r'Creates a mask filled with a default value for the logical ties.'
        self._is_first_window = True
        if self._fader_type == 'out':
            self._mask = [1 for _ in range(self.__len__())]
        else:
            self._mask = [0 for _ in range(self.__len__())]

    def random_mask(self):
        r"Creates a mask randomly filled with ``1``'s and ``0``'s."
        self._is_first_window = True
        self._mask = [random.randint(0, 1) for _ in range(self.__len__())]

    def shuffle_mask(self):
        r"Shuffles the current mask."
        self._is_first_window = True
        random.shuffle(self._mask)

    ### PRIVATE METHODS ###

    def _remove_element(self):
        r'Sets a random element of the mask to ``False``.'
        for n in range(random.randint(1, self._max_steps)):
            if 1 in self._mask:
                total_count = sum(self._mask)
                random_count = random.randint(0, total_count - 1)
                index = self._get_index_of_nth_occurrence(self._mask,
                                                          1,
                                                          random_count,
                                                          )
                self._mask[index] = 0
            elif n == 0:
                raise RuntimeError("'current_window' is already empty")

    def _add_element(self):
        r'Sets a random element of the mask to ``True``.'
        for n in range(random.randint(1, self._max_steps)):
            if 0 in self._mask:
                total_count = self.__len__() - sum(self._mask)
                random_count = random.randint(0, total_count - 1)
                index = self._get_index_of_nth_occurrence(self._mask,
                                                          0,
                                                          random_count,
                                                          )
                self._mask[index] = 1
            elif n == 0:
                raise RuntimeError("'current_window' is already full")

    def _mask_to_selection(self):
        r'Applies the mask to the current window.'
        dummy_container = copy.deepcopy(self._contents)
        logical_ties = abjad.select(dummy_container).logical_ties(pitched=True)
        for mask_value, logical_tie in zip(self._mask, logical_ties):
            if mask_value == 0:
                for leaf in logical_tie:
                    abjad.mutate(leaf).replace(
                        abjad.Rest(leaf.written_duration))
        # applying time signatures and rewrite meter
        enforce_time_signature(
            dummy_container,
            self._time_signatures,
            disable_rewrite_meter=self._disable_rewrite_meter,
            boundary_depth=self._boundary_depth,
            maximum_dot_count=self._maximum_dot_count,
            rewrite_tuplets=self._rewrite_tuplets,
        )
        if self._use_multimeasure_rests:
            rests_to_multimeasure_rest(dummy_container)
        # output
        self._current_window = dummy_container[:]
        dummy_container[:] = []
        self._is_first_window = False

    @staticmethod
    def _remove_all_time_signatures(container):
        r'Removes all time signatures of an ``abjad.Container``'
        for leaf in abjad.select(container).leaves():
            if abjad.inspect(leaf).effective(abjad.TimeSignature):
                abjad.detach(abjad.TimeSignature, leaf)

    @staticmethod
    def _get_index_of_nth_occurrence(input_list: list,
                                     entry: Any,
                                     count: int,
                                     ) -> int:
        return tuple(index for index, item in enumerate(input_list)
                     if item == entry)[count]

    ### PUBLIC PROPERTIES ###

    @property
    def contents(self) -> abjad.Container:
        r'The ``abjad.Container`` to be faded.'
        return copy.deepcopy(self._contents)

    @contents.setter
    def contents(self,
                 contents: abjad.Container,
                 ):
        if not isinstance(contents, abjad.Container):
            raise TypeError("'contents' must be 'abjad.Container' or "
                            "child class")
        self._contents = copy.deepcopy(contents)
        dummy_container = copy.deepcopy(self._contents)
        self._current_window = dummy_container[:]
        dummy_container[:] = []
        self._time_signatures = time_signature_extractor(contents,
                                                         do_not_use_none=True,
                                                         )
        self.reset_mask()
        self._is_first_window = True

    @property
    def current_window(self) -> abjad.Selection:
        r'Read-only property, returns the previously output selection.'
        current_window = copy.deepcopy(self._current_window)
        if self._omit_time_signatures:
            self._remove_all_time_signatures(current_window)
        return current_window

    @property
    def fader_type(self) -> str:
        r"Type of fading, must be either ``'in'`` or ``'out'``."
        return self._fader_type

    @fader_type.setter
    def fader_type(self,
                   fader_type: str,
                   ):
        if not isinstance(fader_type, str):
            raise TypeError("'fader_type' must be 'str'")
        if fader_type not in ('in', 'out'):
            raise ValueError("'fader_type' must be either 'in' or 'out'")
        self._fader_type = fader_type

    @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,
                  ):
        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 mask(self) -> list:
        r"Mask with ``1``'s and ``0``'s representing the logical ties.'"
        return self._mask

    @mask.setter
    def mask(self,
             mask: list,
             ):
        if not isinstance(mask, list):
            raise TypeError("'mask' must be 'list'")
        if any(element not in (0, 1) for element in mask):
            raise ValueError("'mask' must contain only 1's and 0's")
        if len(mask) != self.__len__():
            raise ValueError("'mask' must have the same length as the number "
                             "of logical ties in 'contents'")
        self._mask = mask
        self._is_first_window = True

    @property
    def disable_rewrite_meter(self) -> bool:
        r"""When ``True``, the durations of the notes in the output will not be
        rewritten by the ``rewrite_meter`` mutation.
        """
        return self._disable_rewrite_meter

    @disable_rewrite_meter.setter
    def disable_rewrite_meter(self,
                              disable_rewrite_meter: bool,
                              ):
        if not isinstance(disable_rewrite_meter, bool):
            raise TypeError("'disable_rewrite_meter' must be 'bool'")
        self._disable_rewrite_meter = disable_rewrite_meter

    @property
    def omit_time_signatures(self) -> bool:
        r'When ``True``, all time signatures will be omitted from the output.'
        return self._omit_time_signatures

    @omit_time_signatures.setter
    def omit_time_signatures(self,
                             omit_time_signatures: bool,
                             ):
        if not isinstance(omit_time_signatures, bool):
            raise TypeError("'omit_time_signatures' must be 'bool'")
        self._omit_time_signatures = omit_time_signatures

    @property
    def use_multimeasure_rests(self) -> bool:
        r'When ``True``, multimeasure rests will be used for silent measures.'
        return self._use_multimeasure_rests

    @use_multimeasure_rests.setter
    def use_multimeasure_rests(self,
                               use_multimeasure_rests: bool,
                               ):
        if not isinstance(use_multimeasure_rests, bool):
            raise TypeError("'use_multimeasure_rests' must be 'bool'")
        self._use_multimeasure_rests = use_multimeasure_rests

    @property
    def boundary_depth(self) -> Union[int, None]:
        r"Sets the argument ``boundary_depth`` of abjad's ``rewrite_meter()``."
        return self._boundary_depth

    @boundary_depth.setter
    def boundary_depth(self,
                       boundary_depth: Optional[int],
                       ):
        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's
        ``rewrite_meter()``.
        """
        return self._maximum_dot_count

    @maximum_dot_count.setter
    def maximum_dot_count(self,
                          maximum_dot_count: Optional[int],
                          ):
        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's
        ``rewrite_meter()``.
        """
        return self._rewrite_tuplets

    @rewrite_tuplets.setter
    def rewrite_tuplets(self,
                        rewrite_tuplets: bool,
                        ):
        if not isinstance(rewrite_tuplets, bool):
            raise TypeError("'rewrite_tuplets' must be 'bool'")
        self._rewrite_tuplets = rewrite_tuplets

    @property
    def processs_on_first_call(self) -> bool:
        r"""If ``True`` then the ``contents`` will be processed in the very
        first call.
        """
        return self._processs_on_first_call

    @processs_on_first_call.setter
    def processs_on_first_call(self,
                               processs_on_first_call: bool,
                               ):
        if not isinstance(processs_on_first_call, bool):
            raise TypeError("'processs_on_first_call' must be 'bool'")
        self._processs_on_first_call = processs_on_first_call

    ### PRIVATE PROPERTIES ###

    @property
    def _done(self) -> bool:
        r"""Boolean indicating whether the process is done, which is when the
        mask is filled with ``1``'s with ``fader_type`` set to ``'in'`` or when
        the mask is filled with ``0``'s with ``fader_type`` set to ``'out'``.
        """
        if self._fader_type == 'out':
            return 1 not in self._mask
        else:
            return 0 not in self._mask
