Source code for abjadext.rmakers.classes

"""
The rmakers classes.
"""
import dataclasses
import typing

import abjad


[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True) class Incise: """ Incise specifier. """ body_ratio: tuple[int, ...] = (1,) fill_with_rests: bool = False outer_tuplets_only: bool = False prefix_counts: typing.Sequence[int] = () prefix_talea: typing.Sequence[int] = () suffix_counts: typing.Sequence[int] = () suffix_talea: typing.Sequence[int] = () talea_denominator: int | None = None __documentation_section__ = "Specifiers"
[docs] def __post_init__(self): assert isinstance(self.prefix_talea, typing.Sequence), repr(self.prefix_talea) assert self._is_integer_tuple(self.prefix_talea) assert isinstance(self.prefix_counts, typing.Sequence), repr(self.prefix_counts) assert self._is_length_tuple(self.prefix_counts) if self.prefix_talea: assert self.prefix_counts assert isinstance(self.suffix_talea, typing.Sequence), repr(self.suffix_talea) assert self._is_integer_tuple(self.suffix_talea) assert isinstance(self.suffix_counts, typing.Sequence), repr(self.suffix_counts) assert self._is_length_tuple(self.suffix_counts) if self.suffix_talea: assert self.suffix_counts if self.talea_denominator is not None: assert abjad.math.is_nonnegative_integer_power_of_two( self.talea_denominator ) if self.prefix_talea or self.suffix_talea: assert self.talea_denominator is not None assert isinstance(self.body_ratio, tuple), repr(self.body_ratio) assert isinstance(self.fill_with_rests, bool), repr(self.fill_with_rests) assert isinstance(self.outer_tuplets_only, bool), repr(self.outer_tuplets_only)
@staticmethod def _is_integer_tuple(argument): if argument is None: return True if all(isinstance(_, int) for _ in argument): return True return False @staticmethod def _is_length_tuple(argument): if argument is None: return True if abjad.math.all_are_nonnegative_integer_equivalent_numbers(argument): if isinstance(argument, tuple | list): return True return False @staticmethod def _reverse_tuple(argument): if argument is not None: return tuple(reversed(argument))
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True) class Interpolation: """ Interpolation specifier. """ start_duration: abjad.Duration = abjad.Duration(1, 8) stop_duration: abjad.Duration = abjad.Duration(1, 16) written_duration: abjad.Duration = abjad.Duration(1, 16) __documentation_section__ = "Specifiers"
[docs] def __post_init__(self) -> None: assert isinstance(self.start_duration, abjad.Duration), repr( self.start_duration ) assert isinstance(self.stop_duration, abjad.Duration), repr(self.stop_duration) assert isinstance(self.written_duration, abjad.Duration), repr( self.written_duration )
[docs] def reverse(self) -> "Interpolation": """ Swaps start duration and stop duration of interpolation specifier. Changes accelerando specifier to ritardando specifier: .. container:: example >>> specifier = rmakers.Interpolation( ... start_duration=abjad.Duration(1, 4), ... stop_duration=abjad.Duration(1, 16), ... written_duration=abjad.Duration(1, 16), ... ) >>> specifier.reverse() Interpolation(start_duration=Duration(1, 16), stop_duration=Duration(1, 4), written_duration=Duration(1, 16)) .. container:: example Changes ritardando specifier to accelerando specifier: >>> specifier = rmakers.Interpolation( ... start_duration=abjad.Duration(1, 16), ... stop_duration=abjad.Duration(1, 4), ... written_duration=abjad.Duration(1, 16), ... ) >>> specifier.reverse() Interpolation(start_duration=Duration(1, 4), stop_duration=Duration(1, 16), written_duration=Duration(1, 16)) """ return type(self)( start_duration=self.stop_duration, stop_duration=self.start_duration, written_duration=self.written_duration, )
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True) class Spelling: r""" Duration spelling specifier. .. container:: example Decreases monotically: >>> def make_lilypond_file(pairs): ... time_signatures = rmakers.time_signatures(pairs) ... durations = [abjad.Duration(_) for _ in pairs] ... tuplets = rmakers.talea( ... durations, ... [5], ... 16, ... spelling=rmakers.Spelling(increase_monotonic=False), ... ) ... lilypond_file_ = rmakers.example(tuplets, time_signatures) ... voice = lilypond_file_["Voice"] ... rmakers.beam(voice) ... rmakers.extract_trivial(voice) ... return lilypond_file_ >>> lilypond_file = make_lilypond_file([(3, 4), (3, 4)]) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> score = lilypond_file["Score"] >>> string = abjad.lilypond(score) >>> print(string) \context Score = "Score" { \context RhythmicStaff = "Staff" \with { \override Clef.stencil = ##f } { \context Voice = "Voice" { \time 3/4 c'4 ~ c'16 c'4 ~ c'16 [ c'8 ] ~ c'8. c'4 ~ c'16 c'4 } } } .. container:: example Increases monotically: >>> def make_lilypond_file(pairs): ... time_signatures = rmakers.time_signatures(pairs) ... durations = [abjad.Duration(_) for _ in time_signatures] ... tuplets = rmakers.talea( ... durations, ... [5], ... 16, ... spelling=rmakers.Spelling(increase_monotonic=True), ... ) ... lilypond_file_ = rmakers.example(tuplets, time_signatures) ... voice = lilypond_file_["Voice"] ... rmakers.beam(voice) ... rmakers.extract_trivial(voice) ... return lilypond_file_ >>> pairs = [(3, 4), (3, 4)] >>> lilypond_file = make_lilypond_file(pairs) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> score = lilypond_file["Score"] >>> string = abjad.lilypond(score) >>> print(string) \context Score = "Score" { \context RhythmicStaff = "Staff" \with { \override Clef.stencil = ##f } { \context Voice = "Voice" { \time 3/4 c'16 ~ c'4 c'16 ~ c'4 c'8 ~ c'8. [ c'16 ] ~ c'4 c'4 } } } .. container:: example Forbids note durations equal to ``1/4`` or greater: >>> def make_lilypond_file(pairs): ... time_signatures = rmakers.time_signatures(pairs) ... durations = [abjad.Duration(_) for _ in time_signatures] ... tuplets = rmakers.talea( ... durations, ... [1, 1, 1, 1, 4, -4], ... 16, ... spelling=rmakers.Spelling( ... forbidden_note_duration=abjad.Duration(1, 4) ... ), ... ) ... lilypond_file_ = rmakers.example(tuplets, time_signatures) ... voice = lilypond_file_["Voice"] ... rmakers.beam(voice) ... rmakers.extract_trivial(voice) ... return lilypond_file_ >>> pairs = [(3, 4), (3, 4)] >>> lilypond_file = make_lilypond_file(pairs) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> score = lilypond_file["Score"] >>> string = abjad.lilypond(score) >>> print(string) \context Score = "Score" { \context RhythmicStaff = "Staff" \with { \override Clef.stencil = ##f } { \context Voice = "Voice" { \time 3/4 c'16 [ c'16 c'16 c'16 c'8 ~ c'8 ] r4 c'16 [ c'16 c'16 c'16 c'8 ~ c'8 ] r4 } } } .. container:: example Forbids rest durations equal to ``1/4`` or greater: >>> def make_lilypond_file(pairs): ... time_signatures = rmakers.time_signatures(pairs) ... durations = [abjad.Duration(_) for _ in time_signatures] ... tuplets = rmakers.talea( ... durations, ... [1, 1, 1, 1, 4, -4], ... 16, ... spelling=rmakers.Spelling( ... forbidden_rest_duration=abjad.Duration(1, 4) ... ), ... ) ... lilypond_file_ = rmakers.example(tuplets, time_signatures) ... voice = lilypond_file_["Voice"] ... rmakers.beam(voice) ... rmakers.extract_trivial(voice) ... return lilypond_file_ >>> pairs = [(3, 4), (3, 4)] >>> lilypond_file = make_lilypond_file(pairs) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> score = lilypond_file["Score"] >>> string = abjad.lilypond(score) >>> print(string) \context Score = "Score" { \context RhythmicStaff = "Staff" \with { \override Clef.stencil = ##f } { \context Voice = "Voice" { \time 3/4 c'16 [ c'16 c'16 c'16 ] c'4 r8 r8 c'16 [ c'16 c'16 c'16 ] c'4 r8 r8 } } } .. container:: example Spells nonassignable durations with monontonically decreasing durations: >>> def make_lilypond_file(pairs): ... time_signatures = rmakers.time_signatures(pairs) ... durations = [abjad.Duration(_) for _ in time_signatures] ... tuplets = rmakers.talea( ... durations, ... [5], ... 16, ... spelling=rmakers.Spelling(increase_monotonic=False), ... ) ... container = abjad.Container(tuplets) ... rmakers.beam(container) ... rmakers.extract_trivial(container) ... components = abjad.mutate.eject_contents(container) ... lilypond_file = rmakers.example(components, time_signatures) ... return lilypond_file >>> pairs = [(5, 8), (5, 8), (5, 8)] >>> lilypond_file = make_lilypond_file(pairs) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> score = lilypond_file["Score"] >>> string = abjad.lilypond(score) >>> print(string) \context Score = "Score" { \context RhythmicStaff = "Staff" \with { \override Clef.stencil = ##f } { \context Voice = "Voice" { \time 5/8 c'4 ~ c'16 c'4 ~ c'16 c'4 ~ c'16 c'4 ~ c'16 c'4 ~ c'16 c'4 ~ c'16 } } } .. container:: example Spells nonassignable durations with monontonically increasing durations: >>> def make_lilypond_file(pairs): ... time_signatures = rmakers.time_signatures(pairs) ... durations = [abjad.Duration(_) for _ in time_signatures] ... tuplets = rmakers.talea( ... durations, ... [5], ... 16, ... spelling=rmakers.Spelling(increase_monotonic=True), ... ) ... container = abjad.Container(tuplets) ... rmakers.beam(container) ... rmakers.extract_trivial(container) ... components = abjad.mutate.eject_contents(container) ... lilypond_file = rmakers.example(components, time_signatures) ... return lilypond_file >>> pairs = [(5, 8), (5, 8), (5, 8)] >>> lilypond_file = make_lilypond_file(pairs) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> score = lilypond_file["Score"] >>> string = abjad.lilypond(score) >>> print(string) \context Score = "Score" { \context RhythmicStaff = "Staff" \with { \override Clef.stencil = ##f } { \context Voice = "Voice" { \time 5/8 c'16 ~ c'4 c'16 ~ c'4 c'16 ~ c'4 c'16 ~ c'4 c'16 ~ c'4 c'16 ~ c'4 } } } .. container:: example Forbids durations equal to ``1/4`` or greater: >>> def make_lilypond_file(pairs): ... time_signatures = rmakers.time_signatures(pairs) ... durations = [abjad.Duration(_) for _ in time_signatures] ... tuplets = rmakers.talea( ... durations, [1, 1, 1, 1, 4, 4], 16, ... spelling=rmakers.Spelling( ... forbidden_note_duration=abjad.Duration(1, 4) ... ), ... ) ... lilypond_file = rmakers.example(tuplets, time_signatures) ... voice = lilypond_file["Voice"] ... rmakers.beam(voice) ... rmakers.extract_trivial(voice) ... return lilypond_file >>> pairs = [(3, 4), (3, 4)] >>> lilypond_file = make_lilypond_file(pairs) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> score = lilypond_file["Score"] >>> string = abjad.lilypond(score) >>> print(string) \context Score = "Score" { \context RhythmicStaff = "Staff" \with { \override Clef.stencil = ##f } { \context Voice = "Voice" { \time 3/4 c'16 [ c'16 c'16 c'16 c'8 ~ c'8 c'8 ~ c'8 ] c'16 [ c'16 c'16 c'16 c'8 ~ c'8 c'8 ~ c'8 ] } } } Rewrites forbidden durations with smaller durations tied together. """ forbidden_note_duration: abjad.Duration | None = None forbidden_rest_duration: abjad.Duration | None = None increase_monotonic: bool = False __documentation_section__ = "Specifiers"
[docs] def __post_init__(self): if self.forbidden_note_duration is not None: assert isinstance(self.forbidden_note_duration, abjad.Duration), repr( self.forbidden_note_duration ) if self.forbidden_rest_duration is not None: assert isinstance(self.forbidden_rest_duration, abjad.Duration), repr( self.forbidden_rest_duration ) assert isinstance(self.increase_monotonic, bool), repr(self.increase_monotonic)
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True) class Talea: """ Talea specifier. .. container:: example >>> talea = rmakers.Talea( ... [2, 1, 3, 2, 4, 1, 1], ... 16, ... preamble=[1, 1, 1, 1], ... ) .. container:: example Equal to weight of counts: >>> rmakers.Talea([1, 2, 3, 4], 16).period 10 Rests make no difference: >>> rmakers.Talea([1, 2, -3, 4], 16).period 10 Denominator makes no difference: >>> rmakers.Talea([1, 2, -3, 4], 32).period 10 Preamble makes no difference: >>> talea = rmakers.Talea( ... [1, 2, -3, 4], ... 32, ... preamble=[1, 1, 1], ... ) >>> talea.period 10 .. container:: example >>> talea = rmakers.Talea( ... [2, 1, 3, 2, 4, 1, 1], ... 16, ... preamble=[1, 1, 1, 1], ... ) >>> talea.preamble [1, 1, 1, 1] .. container:: example >>> talea = rmakers.Talea( ... [16, -4, 16], ... 16, ... preamble=[1], ... ) >>> for i, duration in enumerate(talea): ... duration ... Duration(1, 16) Duration(1, 1) Duration(-1, 4) Duration(1, 1) """ counts: typing.Sequence[int | str] denominator: int end_counts: typing.Sequence[int] = () preamble: typing.Sequence[int] = () __documentation_section__ = "Specifiers"
[docs] def __post_init__(self): assert isinstance(self.counts, typing.Sequence), repr(self.counts) for count in self.counts: assert isinstance(count, int) or count in "+-", repr(count) assert abjad.math.is_nonnegative_integer_power_of_two(self.denominator) assert isinstance(self.end_counts, typing.Sequence), repr(self.end_counts) assert all(isinstance(_, int) for _ in self.end_counts) assert isinstance(self.preamble, typing.Sequence), repr(self.preamble) assert all(isinstance(_, int) for _ in self.preamble)
[docs] def __contains__(self, argument: int) -> bool: """ Is true when talea contains ``argument``. With preamble: .. container:: example >>> talea = rmakers.Talea( ... [10], ... 16, ... preamble=[1, -1, 1], ... ) >>> for i in range(1, 23 + 1): ... i, i in talea ... (1, True) (2, True) (3, True) (4, False) (5, False) (6, False) (7, False) (8, False) (9, False) (10, False) (11, False) (12, False) (13, True) (14, False) (15, False) (16, False) (17, False) (18, False) (19, False) (20, False) (21, False) (22, False) (23, True) """ assert isinstance(argument, int), repr(argument) assert 0 < argument, repr(argument) if self.preamble: preamble = [abs(_) for _ in self.preamble] cumulative = abjad.math.cumulative_sums(preamble)[1:] if argument in cumulative: return True preamble_weight = abjad.sequence.weight(preamble) else: preamble_weight = 0 if self.counts is not None: counts = [abs(_) for _ in self.counts] else: counts = [] cumulative = abjad.math.cumulative_sums(counts)[:-1] argument -= preamble_weight argument %= self.period return argument in cumulative
[docs] def __getitem__(self, argument) -> tuple[int, int] | list[tuple[int, int]]: """ Gets item or slice identified by ``argument``. Gets item at index: .. container:: example >>> talea = rmakers.Talea( ... [2, 1, 3, 2, 4, 1, 1], ... 16, ... preamble=[1, 1, 1, 1], ... ) >>> talea[0] (1, 16) >>> talea[1] (1, 16) .. container:: example Gets items in slice: >>> for duration in talea[:6]: ... duration ... (1, 16) (1, 16) (1, 16) (1, 16) (2, 16) (1, 16) >>> for duration in talea[2:8]: ... duration ... (1, 16) (1, 16) (2, 16) (1, 16) (3, 16) (2, 16) """ preamble: list[int | str] = list(self.preamble) counts = list(self.counts) counts_ = abjad.CyclicTuple(preamble + counts) if isinstance(argument, int): count = counts_.__getitem__(argument) return (count, self.denominator) elif isinstance(argument, slice): counts_ = counts_.__getitem__(argument) result = [(count, self.denominator) for count in counts_] return result raise ValueError(argument)
[docs] def __iter__(self) -> typing.Iterator[abjad.Duration]: """ Iterates talea. .. container:: example >>> talea = rmakers.Talea( ... [2, 1, 3, 2, 4, 1, 1], ... 16, ... preamble=[1, 1, 1, 1], ... ) >>> for duration in talea: ... duration ... Duration(1, 16) Duration(1, 16) Duration(1, 16) Duration(1, 16) Duration(1, 8) Duration(1, 16) Duration(3, 16) Duration(1, 8) Duration(1, 4) Duration(1, 16) Duration(1, 16) """ for count in self.preamble or []: duration = abjad.Duration(count, self.denominator) yield duration for item in self.counts or []: assert isinstance(item, int) duration = abjad.Duration(item, self.denominator) yield duration
[docs] def __len__(self) -> int: """ Gets length. .. container:: example >>> len(rmakers.Talea([2, 1, 3, 2, 4, 1, 1], 16)) 7 Defined equal to length of counts. """ return len(self.counts or [])
@property def period(self) -> int: """ Gets period of talea. .. container:: example Equal to weight of counts: >>> rmakers.Talea([1, 2, 3, 4], 16).period 10 Rests make no difference: >>> rmakers.Talea([1, 2, -3, 4], 16).period 10 Denominator makes no difference: >>> rmakers.Talea([1, 2, -3, 4], 32).period 10 Preamble makes no difference: >>> talea = rmakers.Talea( ... [1, 2, -3, 4], ... 32, ... preamble=[1, 1, 1], ... ) >>> talea.period 10 """ return abjad.sequence.weight(self.counts)
[docs] def advance(self, weight: int) -> "Talea": """ Advances talea by ``weight``. .. container:: example >>> talea = rmakers.Talea( ... [2, 1, 3, 2, 4, 1, 1], ... 16, ... preamble=[1, 1, 1, 1], ... ) >>> talea.advance(0) Talea(counts=[2, 1, 3, 2, 4, 1, 1], denominator=16, end_counts=(), preamble=[1, 1, 1, 1]) >>> talea.advance(1) Talea(counts=[2, 1, 3, 2, 4, 1, 1], denominator=16, end_counts=(), preamble=[1, 1, 1]) >>> talea.advance(2) Talea(counts=[2, 1, 3, 2, 4, 1, 1], denominator=16, end_counts=(), preamble=[1, 1]) >>> talea.advance(3) Talea(counts=[2, 1, 3, 2, 4, 1, 1], denominator=16, end_counts=(), preamble=[1]) >>> talea.advance(4) Talea(counts=[2, 1, 3, 2, 4, 1, 1], denominator=16, end_counts=(), preamble=()) >>> talea.advance(5) Talea(counts=[2, 1, 3, 2, 4, 1, 1], denominator=16, end_counts=(), preamble=[1, 1, 3, 2, 4, 1, 1]) >>> talea.advance(6) Talea(counts=[2, 1, 3, 2, 4, 1, 1], denominator=16, end_counts=(), preamble=[1, 3, 2, 4, 1, 1]) >>> talea.advance(7) Talea(counts=[2, 1, 3, 2, 4, 1, 1], denominator=16, end_counts=(), preamble=[3, 2, 4, 1, 1]) >>> talea.advance(8) Talea(counts=[2, 1, 3, 2, 4, 1, 1], denominator=16, end_counts=(), preamble=[2, 2, 4, 1, 1]) .. container:: example REGRESSION. Works when talea advances by period of talea: >>> talea = rmakers.Talea([1, 2, 3, 4], 16) >>> talea Talea(counts=[1, 2, 3, 4], denominator=16, end_counts=(), preamble=()) >>> talea.advance(10) Talea(counts=[1, 2, 3, 4], denominator=16, end_counts=(), preamble=()) >>> talea.advance(20) Talea(counts=[1, 2, 3, 4], denominator=16, end_counts=(), preamble=()) """ assert isinstance(weight, int), repr(weight) if weight < 0: raise Exception(f"weight {weight} must be nonnegative.") if weight == 0: return dataclasses.replace(self) preamble: list[int | str] = list(self.preamble) counts = list(self.counts) if weight < abjad.sequence.weight(preamble): consumed, remaining = abjad.sequence.split( preamble, [weight], overhang=True ) preamble_ = remaining elif weight == abjad.sequence.weight(preamble): preamble_ = () else: assert abjad.sequence.weight(preamble) < weight weight -= abjad.sequence.weight(preamble) preamble = counts[:] while True: if weight <= abjad.sequence.weight(preamble): break preamble += counts if abjad.sequence.weight(preamble) == weight: consumed, remaining = preamble[:], () else: consumed, remaining = abjad.sequence.split( preamble, [weight], overhang=True ) preamble_ = remaining return dataclasses.replace( self, counts=counts, denominator=self.denominator, preamble=preamble_, )