import abjad
from .. import get, mutate
[docs]class Repeater():
r"""Takes an |abjad.Container| (or child class) as input and outputs an
|abjad.Selection| with ``n`` repetitions. It can be of type unfold or
volta.
Basic usage:
Calling the object will return an |abjad.Selection| generated by the
repeating process, which is of type unfold by default (more on this
below). The argument of :meth:`__call__()` defines the number of
repetitions.
>>> 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/Repeater-aK6JHsJPDM.png
>>> repeater = auxjad.Repeater(container)
>>> notes = repeater(2)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'4
d'4
e'4
f'4
c'4
d'4
e'4
f'4
}
.. figure:: ../_images/Repeater-tigd5dwtszh.png
The property :attr:`current_window` can be used to access the last
results.
>>> notes = repeater.current_window()
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'4
d'4
e'4
f'4
c'4
d'4
e'4
f'4
}
.. figure:: ../_images/Repeater-hg86wd75fvp.png
:attr:`repeat_type`:
Use the :attr:`repeat_type` property to set the type of the repeater.
It takes a single string, which should be either ``'unfold'`` or
``'volta'``. Unfold is the default mode i.e. the repeater outputs a
selection of ``n`` identical consecutive measures. In volta mode,
repeat bars are added as well as a written indication of the number of
repeats. Compare:
>>> container = abjad.Container(r"c'2 d'2")
>>> repeater = auxjad.Repeater(container,
... repeat_type='unfold',
... )
>>> notes = repeater(5)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'2
d'2
c'2
d'2
c'2
d'2
c'2
d'2
c'2
d'2
}
.. figure:: ../_images/Repeater-vQi9k3oMox.png
>>> container = abjad.Container(r"c'2 d'2")
>>> repeater = auxjad.Repeater(container,
... repeat_type='volta',
... )
>>> notes = repeater(5)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\repeat volta 5
{
c'2
d'2
\tweak RehearsalMark.self-alignment-X #RIGHT
\tweak RehearsalMark.break-visibility
#begin-of-line-invisible
\mark \markup{\box "5×"}
}
}
.. figure:: ../_images/Repeater-k8qwVVoMu9.png
Typical usage of volta mode:
The volta mode can be used to construct containers in the following
way:
>>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
>>> repeater = auxjad.Repeater(container,
... repeat_type='volta',
... )
>>> notes = repeater(3)
>>> staff = abjad.Staff(notes)
>>> repeater.contents = abjad.Container(r"g'2 a'2")
>>> notes = repeater(2)
>>> staff.append(notes)
>>> repeater.contents = abjad.Container(r"b'16 c''16 d''16 e''16 r2.")
>>> notes = repeater(5)
>>> staff.append(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\repeat volta 3
{
c'4
d'4
e'4
f'4
\tweak RehearsalMark.self-alignment-X #RIGHT
\tweak RehearsalMark.break-visibility
#begin-of-line-invisible
\mark \markup{\box "3×"}
}
\repeat volta 2
{
g'2
a'2
\tweak RehearsalMark.self-alignment-X #RIGHT
\tweak RehearsalMark.break-visibility
#begin-of-line-invisible
\mark \markup{\box "2×"}
}
\repeat volta 5
{
b'16
c''16
d''16
e''16
r2.
\tweak RehearsalMark.self-alignment-X #RIGHT
\tweak RehearsalMark.break-visibility
#begin-of-line-invisible
\mark \markup{\box "5×"}
}
}
.. figure:: ../_images/Repeater-KbjMitAIs5.png
:attr:`include_2x_volta_text`:
By default, the written indication ``nx`` is added to all repeats in
volta mode. To omit it when ``n`` is ``2``, set
:attr:`include_2x_volta_text` to ``False``:
>>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
>>> repeater = auxjad.Repeater(container,
... repeat_type='volta',
... include_2x_volta_text=False,
... )
>>> notes = repeater(3)
>>> staff = abjad.Staff(notes)
>>> repeater.contents = abjad.Container(r"g'2 a'2")
>>> notes = repeater(2)
>>> staff.append(notes)
>>> repeater.contents = abjad.Container(r"b'16 c''16 d''16 e''16 r2.")
>>> notes = repeater(5)
>>> staff.append(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\repeat volta 3
{
c'4
d'4
e'4
f'4
\tweak RehearsalMark.self-alignment-X #RIGHT
\tweak RehearsalMark.break-visibility
#begin-of-line-invisible
\mark \markup{\box "3×"}
}
\repeat volta 2
{
g'2
a'2
}
\repeat volta 5
{
b'16
c''16
d''16
e''16
r2.
\tweak RehearsalMark.self-alignment-X #RIGHT
\tweak RehearsalMark.break-visibility
#begin-of-line-invisible
\mark \markup{\box "5×"}
}
}
.. figure:: ../_images/Repeater-vW77Zi6NZJ.png
Time signatures:
This class handles different time signatures.
>>> container = abjad.Container(r"\time 3/4 c'2. \time 2/4 r2 g'2")
>>> repeater = auxjad.Repeater(container)
>>> notes = repeater(3)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/4
c'2.
\time 2/4
r2
g'2
\time 3/4
c'2.
\time 2/4
r2
g'2
\time 3/4
c'2.
\time 2/4
r2
g'2
}
.. figure:: ../_images/Repeater-fqkjxhegzmv.png
Underfull containers:
Containers that are not fully filled in are automatically closed by
this class in its output. Containers without a time signature are
assumed to be in ``4/4`` (which is LilyPond's default).
>>> container = abjad.Container(r"c'4 d'4 e'4")
>>> repeater = auxjad.Repeater(container)
>>> notes = repeater(3)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/4
c'4
d'4
e'4
c'4
d'4
e'4
c'4
d'4
e'4
}
.. figure:: ../_images/Repeater-k4hxxghalwh.png
>>> container = abjad.Container(r"\time 3/4 c'4 d'4 e'4 f'2")
>>> repeater = auxjad.Repeater(container)
>>> notes = repeater(2)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/4
c'4
d'4
e'4
\time 2/4
f'2
\time 3/4
c'4
d'4
e'4
\time 2/4
f'2
}
.. figure:: ../_images/Repeater-fee3qe1vdjl.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:`__call__()` and :meth:`output_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 repeating 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")
>>> repeater = auxjad.Repeater(container)
>>> staff = abjad.Staff()
>>> for window in repeater:
... 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
c'4
d'4
e'4
c'4
d'4
e'4
c'4
d'4
e'4
}
.. figure:: ../_images/Repeater-8oouugbk5zc.png
Arguments and properties:
This class can take many optional keyword arguments during its
creation, besides attr:`repeat_type` and attr:`include_2x_volta_text`.
attr:`omit_time_signatures` will remove all time signatures from the
output while :attr:`force_identical_time_signatures` will force all
time signatures (including repeated ones) to be added to the output
(both are ``False`` by default). When set to ``True``, the properties
:attr:`reposition_clefs`, :attr:`reposition_dynamics`, and
:attr:`reposition_slurs` will invoke the mutations
|auxjad.mutate.reposition_clefs()|,
|auxjad.mutate.reposition_dynamics()|, and
|auxjad.mutate.reposition_slurs()| (default values are ``True``).
Check their documentation for more information on how they operate.
>>> container = abjad.Container(r"\time 3/4 c'4 d'4 e'4")
>>> repeater = auxjad.Repeater(container,
... repeat_type='volta',
... include_2x_volta_text=False,
... omit_time_signatures=False,
... force_identical_time_signatures=False,
... reposition_clefs=True,
... reposition_dynamics=True,
... reposition_slurs=True,
... )
>>> repeater.repeat_type
'volta'
>>> repeater.include_2x_volta_text
False
>>> repeater.omit_time_signatures
False
>>> repeater.force_identical_time_signatures
False
>>> repeater.reposition_clefs
True
>>> repeater.reposition_dynamics
True
>>> repeater.reposition_slurs
True
Use the properties below to change these values after initialisation.
>>> repeater.repeat_type = 'unfold'
>>> repeater.include_2x_volta_text = True
>>> repeater.omit_time_signatures = True
>>> repeater.force_identical_time_signatures = True
>>> repeater.reposition_clefs = False
>>> repeater.reposition_dynamics = False
>>> repeater.reposition_slurs = False
>>> repeater.repeat_type
'unfold'
>>> repeater.include_2x_volta_text
True
>>> repeater.omit_time_signatures
True
>>> repeater.force_identical_time_signatures
True
>>> repeater.reposition_clefs
False
>>> repeater.reposition_dynamics
False
>>> repeater.reposition_slurs
False
:attr:`contents`:
Use the :attr:`contents` property to read as well as overwrite the
contents of the repeater.
>>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
>>> repeater = auxjad.Repeater(container)
>>> notes = repeater(2)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'4
d'4
e'4
f'4
c'4
d'4
e'4
f'4
}
.. figure:: ../_images/Repeater-f1kqq128afw.png
>>> repeater.contents = abjad.Container(r"c'16 d'16 e'16 f'16 g'2.")
>>> notes = repeater(2)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'16
d'16
e'16
f'16
g'2.
c'16
d'16
e'16
f'16
g'2.
}
.. figure:: ../_images/Repeater-jblq28xlso.png
:meth:`output_n`:
This is an alias of :meth:`__call__()`. Takes an argument ``n`` for the
number of repetitions.
>>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
>>> repeater = auxjad.Repeater(container)
>>> notes = repeater.output_n(2)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'4
d'4
e'4
f'4
c'4
d'4
e'4
f'4
}
.. figure:: ../_images/Repeater-w0hd2fp2w9e.png
:attr:`omit_time_signatures`:
To disable time signatures altogether, initialise this class with the
keyword argument :attr:`omit_time_signatures` set to ``True`` (default
is ``False``), or use the :attr:`omit_time_signatures` property after
initialisation.
>>> container = abjad.Container(r"c'4 d'4 e'4")
>>> repeater = auxjad.Repeater(container,
... omit_time_signatures=True,
... )
>>> notes = repeater(3)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
c'4
d'4
e'4
c'4
d'4
e'4
c'4
d'4
e'4
}
.. figure:: ../_images/Repeater-vr4af47iwjg.png
:attr:`force_identical_time_signatures`:
To force time signatures in all iterations of the output, initialise
this class with the keyword argument
:attr:`force_identical_time_signatures` set to ``True`` (default is
``False``), or use the :attr:`force_identical_time_signatures` property
after initialisation.
>>> container = abjad.Container(r"\time 5/4 c'2. d'4 e'4")
>>> repeater = auxjad.Repeater(container,
... force_identical_time_signatures=True,
... )
>>> notes = repeater(3)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 5/4
c'2.
d'4
e'4
\time 5/4
c'2.
d'4
e'4
\time 5/4
c'2.
d'4
e'4
}
.. figure:: ../_images/Repeater-xgpndbdb0j.png
Dynamics, slurs, and clefs:
By default, this class automatically handles dynamics, slurs, and
clefs, optimising their position and omitting repetitions.
>>> container = abjad.Container(r"\clef bass f4\pp( e4) d4(")
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/4
\clef "bass"
f4
\pp
(
e4
)
d4
(
}
.. figure:: ../_images/Repeater-m1ibei9s3am.png
This is done by invoking |auxjad.mutate.reposition_clefs()|,
|auxjad.mutate.reposition_dynamics()|, and
|auxjad.mutate.reposition_slurs()|. Check their documentation for
more information on how they operate.
>>> repeater = auxjad.Repeater(container)
>>> notes = repeater(3)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/4
\clef "bass"
f4
\pp
(
e4
)
d4
(
f4
e4
)
d4
(
f4
e4
)
d4
}
.. figure:: ../_images/Repeater-scjj2uwz2p.png
Set theproperties :attr:`reposition_clefs`,
:attr:`reposition_dynamics`, and :attr:`reposition_slurs` to ``False``
to not invoke these mutations.
>>> repeater = auxjad.Repeater(container,
... reposition_clefs=False,
... reposition_dynamics=False,
... reposition_slurs=False,
... )
>>> notes = repeater(3)
>>> staff = abjad.Staff(notes)
>>> abjad.show(staff)
.. docs::
\new Staff
{
\time 3/4
\clef "bass"
f4
\pp
(
e4
)
d4
(
\clef "bass"
f4
\pp
(
e4
)
d4
(
\clef "bass"
f4
\pp
(
e4
)
d4
(
}
.. figure:: ../_images/Repeater-cc39a0h84dc.png
.. error::
If a container is malformed, i.e. it has an underfilled measure before
a time signature change, this class will raise a :exc:`ValueError`
exception.
>>> container = abjad.Container(r"\time 5/4 g''1 \time 4/4 f'1")
>>> repeater = auxjad.Repeater(container)
ValueError: 'contents' is malformed, with an underfull measure
preceding a time signature change
"""
### CLASS VARIABLES ###
__slots__ = ('_contents',
'_current_window',
'_omit_time_signatures',
'_force_identical_time_signatures',
'_reposition_clefs',
'_reposition_dynamics',
'_reposition_slurs',
'_repeat_type',
'_include_2x_volta_text',
)
### INITIALISER ###
[docs] def __init__(self,
contents: abjad.Container,
*,
omit_time_signatures: bool = False,
force_identical_time_signatures: bool = False,
reposition_clefs: bool = True,
reposition_dynamics: bool = True,
reposition_slurs: bool = True,
repeat_type: str = 'unfold',
include_2x_volta_text: bool = True,
) -> None:
r'Initialises self.'
self.contents = contents
self.omit_time_signatures = omit_time_signatures
self.force_identical_time_signatures = force_identical_time_signatures
self.reposition_clefs = reposition_clefs
self.reposition_dynamics = reposition_dynamics
self.reposition_slurs = reposition_slurs
self.repeat_type = repeat_type
self.include_2x_volta_text = include_2x_volta_text
### SPECIAL METHODS ###
[docs] def __repr__(self) -> str:
r'Returns interpreter representation of :attr:`contents`.'
return abjad.lilypond(self._contents)
[docs] def __call__(self,
n: int = 1,
) -> abjad.Selection:
r"""Calls the repeater process for ``n`` iterations, returning an
|abjad.Selection|. Default ``n`` is ``1``.
"""
if not isinstance(n, int):
raise TypeError("first positional argument must be 'int'")
if n < 1:
raise ValueError("first positional argument must be a positive "
"'int'")
if self._repeat_type == 'unfold':
self._repeat_unfold(n)
elif self._repeat_type == 'volta':
self._repeat_volta(n)
else:
raise ValueError("'repeat_type' must be either 'unfold' or "
"'volta'")
return self.current_window
[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 output_n(self,
n: int = 1,
) -> abjad.Selection:
r"""Calls the repeater process for ``n`` iterations, returning an
|abjad.Selection|. Default ``n`` is ``1``.
"""
return self.__call__(n)
### PRIVATE METHODS ###
def _repeat_unfold(self,
n: int,
) -> None:
r'Repeats a container ``n`` times.'
dummy_container = abjad.mutate.copy(self._contents)
for _ in range(n - 1):
dummy_container.extend(abjad.mutate.copy(self._contents))
if not self._force_identical_time_signatures:
mutate.remove_repeated_time_signatures(dummy_container[:])
if self._reposition_clefs:
mutate.reposition_clefs(dummy_container[:])
if self._reposition_clefs:
mutate.reposition_dynamics(dummy_container[:])
if self._reposition_clefs:
mutate.reposition_slurs(dummy_container[:])
self._current_window = dummy_container[:]
dummy_container[:] = []
def _repeat_volta(self,
n: int,
) -> None:
r'Adds repetition bars and ``n`` times written indication.'
dummy_contents = abjad.mutate.copy(self._contents)
if n == 1:
self._current_window = dummy_contents[:]
dummy_contents[:] = []
return
repeat = abjad.Repeat(repeat_count=n)
abjad.attach(repeat, dummy_contents)
if n > 2 or (n == 2 and self._include_2x_volta_text):
strings = [
r"\tweak RehearsalMark.self-alignment-X #RIGHT",
r"\tweak RehearsalMark.break-visibility "
+ r"#begin-of-line-invisible",
r'\mark \markup{\box "' + str(n) + r'×"}'
]
for string in strings:
abjad.attach(abjad.LilyPondLiteral(string,
format_slot='after',
),
abjad.select(dummy_contents).leaf(-1),
)
dummy_container = abjad.Container([abjad.mutate.copy(dummy_contents)])
self._current_window = dummy_container[:]
dummy_container[:] = []
dummy_contents[:] = []
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)
### 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.Container()
for component in contents[0].components:
self._contents.append(abjad.mutate.copy(component))
elif isinstance(contents, abjad.Staff):
self._contents = abjad.Container()
for component in contents.components:
self._contents.append(abjad.mutate.copy(component))
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)
try:
if not get.selection_is_full(dummy_container[:]):
mutate.close_container(self._contents)
mutate.close_container(dummy_container)
except ValueError as err:
raise ValueError("'contents' is malformed, with an underfull "
"measure preceding a time signature change"
) from err
self._current_window = dummy_container[:]
dummy_container[:] = []
@property
def current_window(self) -> abjad.Selection:
r'Read-only property, returns the previously output selection.'
current_window = abjad.mutate.copy(self._current_window)
if self._omit_time_signatures:
self._remove_all_time_signatures(current_window)
return current_window
@property
def omit_time_signatures(self) -> bool:
r'When ``True``, all time signatures will be omitted from the output.'
return self._omit_time_signatures
@omit_time_signatures.setter
def omit_time_signatures(self,
omit_time_signatures: bool,
) -> None:
if not isinstance(omit_time_signatures, bool):
raise TypeError("'omit_time_signatures' must be 'bool'")
self._omit_time_signatures = omit_time_signatures
@property
def force_identical_time_signatures(self) -> bool:
r"""When ``True``, all time signatures will be printed in the output,
including repeated ones .
"""
return self._force_identical_time_signatures
@force_identical_time_signatures.setter
def force_identical_time_signatures(
self,
force_identical_time_signatures: bool,
) -> None:
if not isinstance(force_identical_time_signatures, bool):
raise TypeError("'force_identical_time_signatures' must be 'bool'")
self._force_identical_time_signatures = force_identical_time_signatures
@property
def reposition_clefs(self) -> bool:
r'When ``True``, |auxjad.mutate.reposition_clefs()| is invoked.'
return self._reposition_clefs
@reposition_clefs.setter
def reposition_clefs(self,
reposition_clefs: bool,
) -> None:
if not isinstance(reposition_clefs, bool):
raise TypeError("'reposition_clefs' must be 'bool'")
self._reposition_clefs = reposition_clefs
@property
def reposition_dynamics(self) -> bool:
r'When ``True``, |auxjad.mutate.reposition_dynamics()| is invoked.'
return self._reposition_dynamics
@reposition_dynamics.setter
def reposition_dynamics(self,
reposition_dynamics: bool,
) -> None:
if not isinstance(reposition_dynamics, bool):
raise TypeError("'reposition_dynamics' must be 'bool'")
self._reposition_dynamics = reposition_dynamics
@property
def reposition_slurs(self) -> bool:
r'When ``True``, |auxjad.mutate.reposition_slurs()| is invoked.'
return self._reposition_slurs
@reposition_slurs.setter
def reposition_slurs(self,
reposition_slurs: bool,
) -> None:
if not isinstance(reposition_slurs, bool):
raise TypeError("'reposition_slurs' must be 'bool'")
self._reposition_slurs = reposition_slurs
@property
def repeat_type(self) -> bool:
r"Defines the type of repeat, either ``'unfold'`` or ``'volta'``."
return self._repeat_type
@repeat_type.setter
def repeat_type(self,
repeat_type: str,
) -> None:
if not isinstance(repeat_type, str):
raise TypeError("'repeat_type' must be 'str'")
repeat_type = repeat_type.lower()
if repeat_type not in ('unfold', 'volta'):
raise ValueError("'repeat_type' must be either 'unfold' or "
"'volta'")
self._repeat_type = repeat_type
@property
def include_2x_volta_text(self) -> bool:
r"""When ``True``, a written indication for the number of repeats will
be included for ``n=2``. Otherwise, it is included only when repeated
more than two times.
"""
return self._include_2x_volta_text
@include_2x_volta_text.setter
def include_2x_volta_text(self,
include_2x_volta_text: bool,
) -> None:
if not isinstance(include_2x_volta_text, bool):
raise TypeError("'include_2x_volta_text' must be 'bool'")
self._include_2x_volta_text = include_2x_volta_text