From 6b0a8720077d464c42c83af1835eb947ab418ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trevor=20Ba=C4=8Da?= Date: Fri, 3 Jun 2022 15:41:21 +0200 Subject: [PATCH 1/3] Cleaned up abjad.rhythmtrees.parse_rtm_syntax(). Cleaned up abjad.mutate.eject_contents(). --- abjad/__init__.py | 3 +- abjad/configuration.py | 1 - abjad/cyclictuple.py | 4 + abjad/meter.py | 4 +- abjad/mutate.py | 25 +- abjad/rhythmtrees.py | 699 +++++++++++++++++++++++++------------ abjad/score.py | 9 - abjad/tag.py | 197 +---------- tests/test_class_design.py | 2 - 9 files changed, 491 insertions(+), 453 deletions(-) diff --git a/abjad/__init__.py b/abjad/__init__.py index 69385bf27f5..fcf1662efc9 100644 --- a/abjad/__init__.py +++ b/abjad/__init__.py @@ -260,7 +260,7 @@ tie, trill_spanner, ) -from .tag import Line, Tag, activate, deactivate +from .tag import Tag, activate, deactivate from .timespan import OffsetCounter, Timespan, TimespanList from .tweaks import Bundle, Tweak, bundle, tweak from .verticalmoment import ( @@ -368,7 +368,6 @@ "LilyPondOverride", "LilyPondParserError", "LilyPondSetting", - "Line", "Lineage", "LogicalTie", "Marimba", diff --git a/abjad/configuration.py b/abjad/configuration.py index 7254eecb01e..d58fdf42b4d 100644 --- a/abjad/configuration.py +++ b/abjad/configuration.py @@ -609,7 +609,6 @@ def list_all_classes(modules="abjad", ignored_classes=None): - diff --git a/abjad/cyclictuple.py b/abjad/cyclictuple.py index 87f5c13014e..a57f5c11b29 100644 --- a/abjad/cyclictuple.py +++ b/abjad/cyclictuple.py @@ -1,3 +1,4 @@ +import collections import dataclasses import typing @@ -121,3 +122,6 @@ def _get_slice(self, start_index, stop_index): indices = range(start_index, stop_index) result = [self[n] for n in indices] return tuple(result) + + +collections.abc.Sequence.register(CyclicTuple) diff --git a/abjad/meter.py b/abjad/meter.py index 807c1dd5d49..a29bf1f8a80 100644 --- a/abjad/meter.py +++ b/abjad/meter.py @@ -22,8 +22,6 @@ class Meter: """ - Meter. - Meter models a common practice understanding of beats and other levels of rhythmic organization structured as a tree. Meter structure corresponds to the monotonically increasing sequence of factors in the numerator of a given time signature. @@ -1152,7 +1150,7 @@ def rewrite_meter( rewrite_tuplets=True, ): r""" - Rewrites the contents of logical ties in an expression to match ``meter``. + Rewrites ``components`` according to ``meter``. .. container:: example diff --git a/abjad/mutate.py b/abjad/mutate.py index 273fa11d253..2061d7899b5 100644 --- a/abjad/mutate.py +++ b/abjad/mutate.py @@ -870,9 +870,9 @@ def copy(argument, n=1) -> list[_score.Component]: return result -def eject_contents(argument): +def eject_contents(container: _score.Container) -> list[_score.Component]: r""" - Ejects contents from outside-of-score container. + Ejects ``container`` contents. .. container:: example @@ -894,15 +894,13 @@ def eject_contents(argument): d'4 } - Returns container contents as a selection with spanners preserved: - - >>> contents = abjad.mutate.eject_contents(container) - >>> contents + >>> leaves = abjad.mutate.eject_contents(container) + >>> leaves [Note("c'4"), Note("c'4"), Note("d'4"), Note("d'4")] - Container contents can be safely added to a new container: + Leaves can be added to a new container: - >>> staff = abjad.Staff(contents, lilypond_type="RhythmicStaff") + >>> staff = abjad.Staff(leaves, lilypond_type="RhythmicStaff") >>> abjad.show(staff) # doctest: +SKIP .. docs:: @@ -919,19 +917,16 @@ def eject_contents(argument): d'4 } - New container is well formed: - - >>> abjad.wf.wellformed(staff) - True - Old container is empty: >>> container Container() - Returns container contents as selection. """ - return argument._eject_contents() + assert isinstance(container, _score.Container), repr(container) + components = container[:] + container[:] = [] + return components def extract(argument): diff --git a/abjad/rhythmtrees.py b/abjad/rhythmtrees.py index b2ba2d5821a..e92d4d80c19 100644 --- a/abjad/rhythmtrees.py +++ b/abjad/rhythmtrees.py @@ -79,23 +79,24 @@ def _update_offsets_of_entire_tree_if_necessary(self): ### PUBLIC PROPERTIES ### @property - def duration(self): + def duration(self) -> _duration.Duration: """ - The preprolated_duration of the node: + Gets node duration. - >>> rtm = '(1 ((1 (1 1)) (1 (1 1))))' - >>> tree = abjad.rhythmtrees.RhythmTreeParser()(rtm)[0] + .. container:: example + + >>> string = '(1 ((1 (1 1)) (1 (1 1))))' + >>> tree = abjad.rhythmtrees.RhythmTreeParser()(string)[0] - >>> tree.duration - Duration(1, 1) + >>> tree.duration + Duration(1, 1) - >>> tree[1].duration - Duration(1, 2) + >>> tree[1].duration + Duration(1, 2) - >>> tree[1][1].duration - Duration(1, 4) + >>> tree[1][1].duration + Duration(1, 4) - Return ``Duration`` instance. """ return self.prolation * self.preprolated_duration @@ -109,29 +110,41 @@ def parentage_ratios(self): subsequent items are pairs of the preprolated duration of the next node in the parentage and the total preprolated_duration of that node and its siblings: - >>> a = abjad.rhythmtrees.RhythmTreeContainer(preprolated_duration=1) - >>> b = abjad.rhythmtrees.RhythmTreeContainer(preprolated_duration=2) - >>> c = abjad.rhythmtrees.RhythmTreeLeaf(preprolated_duration=3) - >>> d = abjad.rhythmtrees.RhythmTreeLeaf(preprolated_duration=4) - >>> e = abjad.rhythmtrees.RhythmTreeLeaf(preprolated_duration=5) + .. container:: example + + >>> a = abjad.rhythmtrees.RhythmTreeContainer(preprolated_duration=1) + >>> b = abjad.rhythmtrees.RhythmTreeContainer(preprolated_duration=2) + >>> c = abjad.rhythmtrees.RhythmTreeLeaf(preprolated_duration=3) + >>> d = abjad.rhythmtrees.RhythmTreeLeaf(preprolated_duration=4) + >>> e = abjad.rhythmtrees.RhythmTreeLeaf(preprolated_duration=5) - >>> a.extend([b, c]) - >>> b.extend([d, e]) + >>> a.extend([b, c]) + >>> b.extend([d, e]) - >>> a.parentage_ratios - (Duration(1, 1),) + >>> a.parentage_ratios + (Duration(1, 1),) - >>> b.parentage_ratios - (Duration(1, 1), (Duration(2, 1), Duration(5, 1))) + >>> for item in b.parentage_ratios: + ... item + Duration(1, 1) + (Duration(2, 1), Duration(5, 1)) - >>> c.parentage_ratios - (Duration(1, 1), (Duration(3, 1), Duration(5, 1))) + >>> for item in c.parentage_ratios: + ... item + Duration(1, 1) + (Duration(3, 1), Duration(5, 1)) - >>> d.parentage_ratios - (Duration(1, 1), (Duration(2, 1), Duration(5, 1)), (Duration(4, 1), Duration(9, 1))) + >>> for item in d.parentage_ratios: + ... item + Duration(1, 1) + (Duration(2, 1), Duration(5, 1)) + (Duration(4, 1), Duration(9, 1)) - >>> e.parentage_ratios - (Duration(1, 1), (Duration(2, 1), Duration(5, 1)), (Duration(5, 1), Duration(9, 1))) + >>> for item in e.parentage_ratios: + ... item + Duration(1, 1) + (Duration(2, 1), Duration(5, 1)) + (Duration(5, 1), Duration(9, 1)) Returns tuple. """ @@ -149,20 +162,21 @@ def parentage_ratios(self): return tuple(reversed(result)) @property - def preprolated_duration(self): + def preprolated_duration(self) -> _duration.Duration: """ - The node's preprolated_duration in pulses: + Gets node duration in pulses. - >>> node = abjad.rhythmtrees.RhythmTreeLeaf( - ... preprolated_duration=1) - >>> node.preprolated_duration - Duration(1, 1) + .. container:: example + + >>> node = abjad.rhythmtrees.RhythmTreeLeaf( + ... preprolated_duration=1) + >>> node.preprolated_duration + Duration(1, 1) - >>> node.preprolated_duration = 2 - >>> node.preprolated_duration - Duration(2, 1) + >>> node.preprolated_duration = 2 + >>> node.preprolated_duration + Duration(2, 1) - Returns int. """ return self._duration @@ -177,29 +191,29 @@ def preprolated_duration(self, argument): @property def pretty_rtm_format(self): """ - The node's pretty-printed RTM format: + Gets pretty-printed RTM format of node. - >>> rtm = '(1 ((1 (1 1)) (1 (1 1))))' - >>> tree = abjad.rhythmtrees.RhythmTreeParser()(rtm)[0] - >>> print(tree.pretty_rtm_format) - (1 ( - (1 ( - 1 - 1)) + .. container:: example + + >>> string = '(1 ((1 (1 1)) (1 (1 1))))' + >>> tree = abjad.rhythmtrees.RhythmTreeParser()(string)[0] + >>> print(tree.pretty_rtm_format) (1 ( - 1 - 1)))) + (1 ( + 1 + 1)) + (1 ( + 1 + 1)))) Returns string. """ return "\n".join(self._pretty_rtm_format_pieces()) @property - def prolation(self): + def prolation(self) -> _duration.Multiplier: """ - Prolation of rhythm tree node. - - Returns multiplier. + Gets node prolation. """ return _math.cumulative_products(self.prolations)[-1] @@ -208,7 +222,7 @@ def prolations(self): """ Prolations of rhythm tree node. - Returns tuple. + Returns tuple of multipliers. """ prolations = [_duration.Multiplier(1)] pairs = _sequence.nwise(self.parentage) @@ -221,31 +235,32 @@ def prolations(self): return tuple(prolations) @property - def start_offset(self): + def start_offset(self) -> _duration.Offset: """ - The starting offset of a node in a rhythm-tree relative the root. + Gets node start offset. - >>> rtm = '(1 ((1 (1 1)) (1 (1 1))))' - >>> tree = abjad.rhythmtrees.RhythmTreeParser()(rtm)[0] + .. container:: example + + >>> string = '(1 ((1 (1 1)) (1 (1 1))))' + >>> tree = abjad.rhythmtrees.RhythmTreeParser()(string)[0] - >>> tree.start_offset - Offset((0, 1)) + >>> tree.start_offset + Offset((0, 1)) - >>> tree[1].start_offset - Offset((1, 2)) + >>> tree[1].start_offset + Offset((1, 2)) - >>> tree[0][1].start_offset - Offset((1, 4)) + >>> tree[0][1].start_offset + Offset((1, 4)) - Returns Offset instance. """ self._update_offsets_of_entire_tree_if_necessary() return self._offset @property - def stop_offset(self): + def stop_offset(self) -> _duration.Offset: """ - The stopping offset of a node in a rhythm-tree relative the root. + Gets node stop offset. """ return self.start_offset + self.duration @@ -256,25 +271,23 @@ class RhythmTreeLeaf(RhythmTreeMixin, uqbar.containers.UniqueTreeNode): .. container:: example - >>> leaf = abjad.rhythmtrees.RhythmTreeLeaf( - ... preprolated_duration=5, is_pitched=True) - >>> leaf - RhythmTreeLeaf(preprolated_duration=Duration(5, 1), is_pitched=True) - - .. container:: example + Pitched rhythm-tree leaf makes notes: - Calls with a pulse preprolated duration to generate leaves: + >>> leaf = abjad.rhythmtrees.RhythmTreeLeaf( + ... preprolated_duration=5, is_pitched=True + ... ) - >>> result = leaf((1, 8)) - >>> result + >>> leaf((1, 8)) [Note("c'2"), Note("c'8")] .. container:: example - Generates rests when called if ``is_pitched`` is false: + Unpitched rhythm-tree leaf makes rests: - >>> abjad.rhythmtrees.RhythmTreeLeaf( - ... preprolated_duration=7, is_pitched=False)((1, 16)) + >>> leaf = abjad.rhythmtrees.RhythmTreeLeaf( + ... preprolated_duration=7, is_pitched=False + ... ) + >>> leaf((1, 16)) [Rest('r4..')] """ @@ -284,15 +297,9 @@ def __init__(self, preprolated_duration=1, is_pitched=True, name=None): RhythmTreeMixin.__init__(self, preprolated_duration=preprolated_duration) self.is_pitched = is_pitched - def __call__(self, pulse_duration): + def __call__(self, pulse_duration) -> list[_score.Leaf | _score.Tuplet]: """ - Generate Abjad score components: - - >>> leaf = abjad.rhythmtrees.RhythmTreeLeaf(5) - >>> leaf((1, 4)) - [Note("c'1"), Note("c'4")] - - Returns sequence of components. + Makes list of leaves and / or tuplets equal to ``pulse_duration``. """ pulse_duration = _duration.Duration(pulse_duration) total_duration = pulse_duration * self.preprolated_duration @@ -301,9 +308,9 @@ def __call__(self, pulse_duration): return maker(0, total_duration) return maker([None], total_duration) - def __graph__(self, **keywords): + def __graph__(self, **keywords) -> uqbar.graphs.Graph: """ - Graphviz graph of rhythm tree leaf. + Gets Graphviz graph of rhythm tree leaf. """ graph = uqbar.graphs.Graph(name="G") node = uqbar.graphs.Node( @@ -314,54 +321,47 @@ def __graph__(self, **keywords): def __repr__(self) -> str: """ - Gets repr. + Gets interpreter representation of rhythm-tree leaf. """ - if self.name is None: - return f"{type(self).__name__}(preprolated_duration={self.preprolated_duration!r}, is_pitched={self.is_pitched!r})" - else: - return f"{type(self).__name__}(preprolated_duration={self.preprolated_duration!r}, is_pitched={self.is_pitched!r}, name={self.name!r})" + properties = [ + f"preprolated_duration={self.preprolated_duration!r}", + f"is_pitched={self.is_pitched!r}", + ] + if self.name is not None: + properties.append(f"name={self.name!r}") + properties_string = ", ".join(properties) + return f"{type(self).__name__}({properties_string})" def _pretty_rtm_format_pieces(self): return [str(self.preprolated_duration)] @property - def rtm_format(self): + def is_pitched(self) -> bool: """ - RTM format of rhythm tree leaf. - - >>> abjad.rhythmtrees.RhythmTreeLeaf(1, is_pitched=True).rtm_format - '1' - >>> abjad.rhythmtrees.RhythmTreeLeaf(5, is_pitched=False).rtm_format - '-5' - - Returns string. + Is true when rhythm-tree leaf is pitched. """ - if self.is_pitched: - return f"{self.preprolated_duration!s}" - return f"-{self.preprolated_duration!s}" + return self._is_pitched - ### PUBLIC PROPERTIES ### + @is_pitched.setter + def is_pitched(self, argument): + self._is_pitched = bool(argument) @property - def is_pitched(self): + def rtm_format(self) -> str: """ - Gets and sets boolean equal to true if leaf is pitched. + Gets RTM format of rhythm tree leaf. - >>> leaf = abjad.rhythmtrees.RhythmTreeLeaf() - >>> leaf.is_pitched - True + .. container:: example - >>> leaf.is_pitched = False - >>> leaf.is_pitched - False + >>> abjad.rhythmtrees.RhythmTreeLeaf(1, is_pitched=True).rtm_format + '1' + >>> abjad.rhythmtrees.RhythmTreeLeaf(5, is_pitched=False).rtm_format + '-5' - Returns true or false. """ - return self._is_pitched - - @is_pitched.setter - def is_pitched(self, argument): - self._is_pitched = bool(argument) + if self.is_pitched: + return f"{self.preprolated_duration!s}" + return f"-{self.preprolated_duration!s}" class RhythmTreeContainer(RhythmTreeMixin, uqbar.containers.UniqueTreeList): @@ -375,7 +375,7 @@ class RhythmTreeContainer(RhythmTreeMixin, uqbar.containers.UniqueTreeList): >>> container = abjad.rhythmtrees.RhythmTreeContainer( ... preprolated_duration=1, ... children=[], - ... ) + ... ) >>> container RhythmTreeContainer((1, 1)) @@ -404,8 +404,7 @@ class RhythmTreeContainer(RhythmTreeMixin, uqbar.containers.UniqueTreeList): .. container:: example - Call ``RhythmTreeContainer`` with a preprolated_duration to generate a tuplet - structure: + Call ``RhythmTreeContainer`` with a duration to make tuplets: >>> components = container((1, 4)) >>> tuplet = components[0] @@ -425,7 +424,6 @@ class RhythmTreeContainer(RhythmTreeMixin, uqbar.containers.UniqueTreeList): } } - Returns ``RhythmTreeContainer`` instance. """ ### INITIALIZER ### @@ -440,7 +438,7 @@ def __init__(self, children=None, preprolated_duration=1, name=None): ### SPECIAL METHODS ### - def __add__(self, argument): + def __add__(self, argument) -> "RhythmTreeContainer": r""" Concatenate containers self and argument. The operation c = a + b returns a new RhythmTreeContainer c with the content of both a and b, and a @@ -448,20 +446,21 @@ def __add__(self, argument): is non-commutative: the content of the first operand will be placed before the content of the second operand: - >>> a = abjad.rhythmtrees.RhythmTreeParser()('(1 (1 1 1))')[0] - >>> b = abjad.rhythmtrees.RhythmTreeParser()('(2 (3 4))')[0] - >>> c = a + b - >>> c.preprolated_duration - Duration(3, 1) + .. container:: example - >>> for _ in c: _ - RhythmTreeLeaf(preprolated_duration=Duration(1, 1), is_pitched=True) - RhythmTreeLeaf(preprolated_duration=Duration(1, 1), is_pitched=True) - RhythmTreeLeaf(preprolated_duration=Duration(1, 1), is_pitched=True) - RhythmTreeLeaf(preprolated_duration=Duration(3, 1), is_pitched=True) - RhythmTreeLeaf(preprolated_duration=Duration(4, 1), is_pitched=True) + >>> a = abjad.rhythmtrees.RhythmTreeParser()('(1 (1 1 1))')[0] + >>> b = abjad.rhythmtrees.RhythmTreeParser()('(2 (3 4))')[0] + >>> c = a + b + >>> c.preprolated_duration + Duration(3, 1) + + >>> for _ in c: _ + RhythmTreeLeaf(preprolated_duration=Duration(1, 1), is_pitched=True) + RhythmTreeLeaf(preprolated_duration=Duration(1, 1), is_pitched=True) + RhythmTreeLeaf(preprolated_duration=Duration(1, 1), is_pitched=True) + RhythmTreeLeaf(preprolated_duration=Duration(3, 1), is_pitched=True) + RhythmTreeLeaf(preprolated_duration=Duration(4, 1), is_pitched=True) - Returns new RhythmTreeContainer. """ if isinstance(argument, str): argument = RhythmTreeParser()(argument) @@ -475,19 +474,20 @@ def __add__(self, argument): container.extend(argument[:]) return container - def __call__(self, pulse_duration): + def __call__(self, pulse_duration) -> list[_score.Leaf | _score.Tuplet]: r""" - Generates Abjad score components. + Makes list of leaves and /or tuplets equal to ``pulse_duration``. .. container:: example - >>> rtm = '(1 (1 (2 (1 1 1)) 2))' - >>> tree = abjad.rhythmtrees.RhythmTreeParser()(rtm)[0] + >>> string = '(1 (1 (2 (1 1 1)) 2))' + >>> tree = abjad.rhythmtrees.RhythmTreeParser()(string)[0] - >>> tree((1, 4)) + >>> components = tree((1, 4)) + >>> components [Tuplet('5:4', "c'16 { 2/3 c'16 c'16 c'16 } c'8")] - >>> staff = abjad.Staff(tree((1, 4))) + >>> staff = abjad.Staff(components) >>> abjad.show(staff) # doctest: +SKIP .. docs:: @@ -509,7 +509,6 @@ def __call__(self, pulse_duration): } } - Returns list of components. """ def recurse(node, tuplet_duration): @@ -549,42 +548,43 @@ def recurse(node, tuplet_duration): _mutate._extract(component) return result - def __graph__(self, **keywords): + def __graph__(self, **keywords) -> uqbar.graphs.Graph: r""" - The Graph representation of the RhythmTreeContainer: - - >>> rtm = '(1 (1 (2 (1 1 1)) 2))' - >>> tree = abjad.rhythmtrees.RhythmTreeParser()(rtm)[0] - >>> graph = tree.__graph__() - >>> print(format(graph, "graphviz")) - digraph G { - graph [bgcolor=transparent, - truecolor=true]; - node_0 [label="1", - shape=triangle]; - node_1 [label="1", - shape=box]; - node_2 [label="2", - shape=triangle]; - node_3 [label="1", - shape=box]; - node_4 [label="1", - shape=box]; - node_5 [label="1", - shape=box]; - node_6 [label="2", - shape=box]; - node_0 -> node_1; - node_0 -> node_2; - node_0 -> node_6; - node_2 -> node_3; - node_2 -> node_4; - node_2 -> node_5; - } - - >>> abjad.graph(graph) # doctest: +SKIP - - Return ``Graph`` instance. + Graphs rhythm-tree container. + + .. container:: example + + >>> string = '(1 (1 (2 (1 1 1)) 2))' + >>> tree = abjad.rhythmtrees.RhythmTreeParser()(string)[0] + >>> graph = tree.__graph__() + >>> print(format(graph, "graphviz")) + digraph G { + graph [bgcolor=transparent, + truecolor=true]; + node_0 [label="1", + shape=triangle]; + node_1 [label="1", + shape=box]; + node_2 [label="2", + shape=triangle]; + node_3 [label="1", + shape=box]; + node_4 [label="1", + shape=box]; + node_5 [label="1", + shape=box]; + node_6 [label="2", + shape=box]; + node_0 -> node_1; + node_0 -> node_2; + node_0 -> node_6; + node_2 -> node_3; + node_2 -> node_4; + node_2 -> node_5; + } + + >>> abjad.graph(graph) # doctest: +SKIP + """ graph = uqbar.graphs.Graph( name="G", attributes={"bgcolor": "transparent", "truecolor": True} @@ -607,18 +607,16 @@ def __graph__(self, **keywords): ) return graph - def __radd__(self, argument): + def __radd__(self, argument) -> "RhythmTreeContainer": """ Concatenates containers argument and self. - - Returns new RhythmTreeContainer. """ assert isinstance(argument, type(self)) return argument.__add__(self) def __repr__(self) -> str: """ - Gets interpreter representation of rhythm tree container. + Gets interpreter representation of rhythm-tree container. """ class_name = type(self).__name__ numerator, denominator = self.duration.pair @@ -626,23 +624,13 @@ def __repr__(self) -> str: ### PRIVATE METHODS ### - def _get_contents_duration(self): - """ - The total preprolated_duration of the children of a ``RhythmTreeContainer`` - instance: - - >>> rtm = '(1 (1 (2 (1 1 1)) 2))' - >>> tree = abjad.rhythmtrees.RhythmTreeParser()(rtm)[0] - - >>> tree._get_contents_duration() - Duration(5, 1) - - >>> tree[1]._get_contents_duration() - Duration(3, 1) - - Returns int. - """ - return sum([x.preprolated_duration for x in self]) + def _get_contents_duration( + self, + ) -> _duration.Duration | _duration.NonreducedFraction: + result = sum([x.preprolated_duration for x in self]) + prototype = (_duration.Duration, _duration.NonreducedFraction) + assert isinstance(result, prototype), repr(result) + return result def _prepare_setitem_multiple(self, expr): if isinstance(expr, str): @@ -669,16 +657,17 @@ def _pretty_rtm_format_pieces(self): ### PUBLIC PROPERTIES ### @property - def rtm_format(self): + def rtm_format(self) -> str: """ - The node's RTM format: + Gets rhythm-tree container RTM format. + + .. container:: example - >>> rtm = '(1 ((1 (1 1)) (1 (1 1))))' - >>> tree = abjad.rhythmtrees.RhythmTreeParser()(rtm)[0] - >>> tree.rtm_format - '(1 ((1 (1 1)) (1 (1 1))))' + >>> string = '(1 ((1 (1 1)) (1 (1 1))))' + >>> tree = abjad.rhythmtrees.RhythmTreeParser()(string)[0] + >>> tree.rtm_format + '(1 ((1 (1 1)) (1 (1 1))))' - Returns string. """ string = " ".join([x.rtm_format for x in self]) return f"({self.preprolated_duration!s} ({string}))" @@ -690,9 +679,10 @@ class RhythmTreeParser(Parser): .. container:: example - Abjad’s rhythm-tree parser parses a micro-language resembling Ircam’s RTM Lisp - syntax, and generates a sequence of RhythmTree structures, which can be furthered - manipulated by composers, before being converted into an Abjad score object: + Abjad’s rhythm-tree parser parses a micro-language resembling Ircam’s + RTM Lisp syntax, and generates a sequence of rhythm-tree structures. + Composers can maniuplate these structures and then convert them to + Abjad score components. >>> parser = abjad.rhythmtrees.RhythmTreeParser() >>> string = '(3 (1 (1 ((2 (1 1 1)) 2 2 1))))' @@ -850,16 +840,265 @@ def p_toplevel__toplevel__node(self, p): p[0] = p[1] + [p[2]] -def parse_rtm_syntax(rtm): +def parse_rtm_syntax(string: str) -> _score.Container | _score.Leaf | _score.Tuplet: r""" - Parses RTM syntax. + Creates rhythm tree from RTM ``string``; then calls rhythm tree on + quarter-note pulse duration. .. container:: example - Parses tuplet: + A single quarter note: + + >>> result = abjad.rhythmtrees.parse_rtm_syntax("1") + >>> result + Note("c'4") + + >>> abjad.show(result) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(result) + >>> print(string) + c'4 + + A series of quarter notes: + + >>> result = abjad.rhythmtrees.parse_rtm_syntax("1 1 1 1 1 1") + >>> result + Container("c'4 c'4 c'4 c'4 c'4 c'4") + + >>> abjad.show(result) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(result) + >>> print(string) + { + c'4 + c'4 + c'4 + c'4 + c'4 + c'4 + } + + Notes with durations of the form ``n * 1/4``: - >>> rtm = '(1 (1 (1 (1 1)) 1))' - >>> tuplet = abjad.rhythmtrees.parse_rtm_syntax(rtm) + >>> result = abjad.rhythmtrees.parse_rtm_syntax("1 2 3 4 5") + >>> result + Container("c'4 c'2 c'2. c'1 c'1 c'4") + + >>> abjad.show(result) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(result) + >>> print(string) + { + c'4 + c'2 + c'2. + c'1 + c'1 + ~ + c'4 + } + + Notes with durations of the form ``1/n * 1/4``: + + >>> result = abjad.rhythmtrees.parse_rtm_syntax("1 1/2 1/3 1/4 1/5") + >>> result + Container("c'4 c'8 { 8/12 c'8 } c'16 { 16/20 c'16 }") + + >>> abjad.show(result) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(result) + >>> print(string) + { + c'4 + c'8 + \tweak edge-height #'(0.7 . 0) + \times 8/12 + { + c'8 + } + c'16 + \tweak edge-height #'(0.7 . 0) + \times 16/20 + { + c'16 + } + } + + With arbitrary multipliers: + + >>> result = abjad.rhythmtrees.parse_rtm_syntax("1 2/3 3/5") + >>> result + Container("c'4 { 4/6 c'4 } { 16/20 c'8. }") + + >>> abjad.show(result) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(result) + >>> print(string) + { + c'4 + \tweak edge-height #'(0.7 . 0) + \times 4/6 + { + c'4 + } + \tweak edge-height #'(0.7 . 0) + \times 16/20 + { + c'8. + } + } + + .. container:: example + + Divides quarter-note duration into 1 part; results in a note: + + >>> string = "(1 (1))" + >>> result = abjad.rhythmtrees.parse_rtm_syntax(string) + >>> result + Note("c'4") + + >>> abjad.show(result) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(result) + >>> print(string) + c'4 + + Divides quarter-note duration ``1:1``; results in a container: + + >>> string = "(1 (1 1))" + >>> result = abjad.rhythmtrees.parse_rtm_syntax(string) + >>> result + Container("c'8 c'8") + + >>> abjad.show(result) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(result) + >>> print(string) + { + c'8 + c'8 + } + + Divides quarter-note duration ``1:2``; results in a tuplet: + + >>> string = "(1 (1 2))" + >>> result = abjad.rhythmtrees.parse_rtm_syntax(string) + >>> result + Tuplet('3:2', "c'8 c'4") + + >>> abjad.show(result) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(result) + >>> print(string) + \times 2/3 + { + c'8 + c'4 + } + + .. container:: example + + Divides half-note duration into 1 part; results in a note: + + >>> string = "(2 (1))" + >>> result = abjad.rhythmtrees.parse_rtm_syntax(string) + >>> result + Note("c'2") + + >>> abjad.show(result) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(result) + >>> print(string) + c'2 + + Divides half-note duration ``1:1``; results in a container: + + >>> string = "(2 (1 1))" + >>> result = abjad.rhythmtrees.parse_rtm_syntax(string) + >>> result + Container("c'4 c'4") + + >>> abjad.show(result) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(result) + >>> print(string) + { + c'4 + c'4 + } + + Divides half-note duration ``1:2``; results in a tuplet: + + >>> string = "(2 (1 2))" + >>> result = abjad.rhythmtrees.parse_rtm_syntax(string) + >>> result + Tuplet('3:2', "c'4 c'2") + + >>> abjad.show(result) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(result) + >>> print(string) + \times 2/3 + { + c'4 + c'2 + } + + .. container:: example + + Divides three successive quarter-note durations, according + to ratios of ``1``, ``1:1``, ``1:2``: + + >>> string = "(1 (1)) (1 (1 1)) (1 (1 2))" + >>> result = abjad.rhythmtrees.parse_rtm_syntax(string) + >>> result + Container("c'4 c'8 c'8 { 2/3 c'8 c'4 }") + + >>> abjad.show(result) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(result) + >>> print(string) + { + c'4 + c'8 + c'8 + \times 2/3 + { + c'8 + c'4 + } + } + + .. container:: example + + Another example: + + >>> string = "(1 (1 (1 (1 1)) 1))" + >>> tuplet = abjad.rhythmtrees.parse_rtm_syntax(string) >>> abjad.show(tuplet) # doctest: +SKIP .. docs:: @@ -876,10 +1115,10 @@ def parse_rtm_syntax(rtm): .. container:: example - Also supports fractional durations: + Fractional durations are allowed: - >>> rtm = '(3/4 (1 1/2 (4/3 (1 -1/2 1))))' - >>> tuplet = abjad.rhythmtrees.parse_rtm_syntax(rtm) + >>> string = "(3/4 (1 1/2 (4/3 (1 -1/2 1))))" + >>> tuplet = abjad.rhythmtrees.parse_rtm_syntax(string) >>> abjad.show(tuplet) # doctest: +SKIP .. docs:: @@ -900,18 +1139,18 @@ def parse_rtm_syntax(rtm): } } - Returns tuplet or container. """ - result = RhythmTreeParser()(rtm) container = _score.Container() - for node in result: - tuplet = node((1, 4)) - # following line added 2012-08-01. tb. - tuplet = tuplet[0] - if tuplet.trivial(): - container.extend(tuplet[:]) - else: - container.append(tuplet) + rtm_containers = RhythmTreeParser()(string) + prototype = (RhythmTreeLeaf, RhythmTreeContainer) + for node in rtm_containers: + assert isinstance(node, prototype), repr(node) + components = node((1, 4)) + container.extend(components) if len(container) == 1: - return container[0] - return container + result = container[0] + else: + result = container + prototype_ = (_score.Container, _score.Leaf, _score.Tuplet) + assert isinstance(result, prototype_), repr(result) + return result diff --git a/abjad/score.py b/abjad/score.py index eeefd71a6ae..621632e4346 100644 --- a/abjad/score.py +++ b/abjad/score.py @@ -1079,15 +1079,6 @@ def _copy_with_children(self): new_container.append(new_component) return new_container - def _eject_contents(self): - if self._parent is not None: - raise Exception("can not eject contents of in-score container.") - contents = self[:] - for component in contents: - component._set_parent(None) - self._components[:] = [] - return contents - def _format_after_site(self, contributions): result = [] strings = contributions.alphabetize(contributions.after.commands) diff --git a/abjad/tag.py b/abjad/tag.py index 68013f163c4..530d15b0a59 100644 --- a/abjad/tag.py +++ b/abjad/tag.py @@ -211,190 +211,6 @@ def words(self) -> list[str]: return words_ -@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True) -class Line: - r""" - Line in a LilyPond file. - - .. container:: example - - >>> string = r" %@% \with-color %! MEASURE_NUMBER:SM31" - >>> abjad.Line(string) - Line(string=' %@% \\with-color %! MEASURE_NUMBER:SM31') - - """ - - string: str - - def get_tags(self): - r""" - Gets tags. - - .. container:: example - - >>> string = r" %@% \with-color %! MEASURE_NUMBER:SM31" - >>> abjad.Line(string).get_tags() - [Tag(string='MEASURE_NUMBER'), Tag(string='SM31')] - - .. container:: example - - REGRESSION. Works with multiple ``%!`` prefixes: - - >>> string = r" %@% \with-color %! SM31 %! SM32" - >>> line = abjad.Line(string) - >>> line.get_tags() - [Tag(string='SM31'), Tag(string='SM32')] - - Returns list of zero or more strings. - """ - tags = [] - if " %! " in self.string: - for chunk in self.string.split(" %! ")[1:]: - parts = chunk.split() - parts = parts[0].split(":") - tags_ = [Tag(_) for _ in parts] - tags.extend(tags_) - return tags - - def is_active(self): - r""" - Is true when line is active. - - .. container:: example - - >>> string = ' \\clef "treble" %! EXPLICT_CLEF' - >>> abjad.Line(string).is_active() - True - - >>> string = ' %@% \\clef "treble" %! EXPLICT_CLEF' - >>> abjad.Line(string).is_active() - False - - >>> string = ' %%% \\clef "treble" %! EXPLICT_CLEF' - >>> abjad.Line(string).is_active() - False - - Returns true or false. - """ - return not self.is_deactivated() - - def is_deactivated(self): - r""" - Is true when line is deactivated. - - .. container:: example - - >>> string = ' \\clef "treble" %! EXPLICT_CLEF' - >>> abjad.Line(string).is_deactivated() - False - - >>> string = ' %@% \\clef "treble" %! EXPLICT_CLEF' - >>> abjad.Line(string).is_deactivated() - True - - >>> string = ' %%% \\clef "treble" %! EXPLICT_CLEF' - >>> abjad.Line(string).is_deactivated() - True - - Returns true or false. - """ - string = self.string.strip() - if string.startswith("%@%"): - return True - if string.startswith("%%%"): - return True - return False - - def match(self, predicate): - r""" - Is true when ``predicate`` matches tags. - - .. container:: example - - >>> string = r" %@% \with-color %! MEASURE_NUMBER:SM31" - >>> line = abjad.Line(string) - - .. container:: example - - Tags: - - >>> line.match(abjad.Tag("MEASURE_NUMBER")) - True - - >>> line.match(abjad.Tag("SM31")) - True - - >>> line.match(abjad.Tag("%@%")) - False - - >>> line.match(abjad.Tag("with-color")) - False - - >>> line.match(abjad.Tag("%!")) - False - - .. container:: example - - Lambdas: - - >>> line.match(lambda x: any(_ for _ in x if _.string.startswith("M"))) - True - - >>> line.match(lambda x: any(_ for _ in x if _.string.startswith("S"))) - True - - >>> line.match(lambda x: any(_ for _ in x if _.string[0] in "SM")) - True - - .. container:: example - - Functions: - - >>> def predicate(tags): - ... if abjad.Tag("SM31") in tags and abjad.Tag("MEASURE_NUMBER") in tags: - ... return True - ... else: - ... return False - - >>> line.match(predicate) - True - - >>> def predicate(tags): - ... if abjad.Tag("SM31") in tags and abjad.Tag("MEASURE_NUMBER") not in tags: - ... return True - ... else: - ... return False - - >>> line.match(predicate) - False - - .. container:: example - - REGRESSION. Works with multiple ``%!`` prefixes: - - >>> string = r" %@% \with-color %! SM31 %! SM32" - >>> line = abjad.Line(string) - - >>> line.match(abjad.Tag("SM31")) - True - - >>> line.match(abjad.Tag("SM32")) - True - - Returns true or false. - """ - if not callable(predicate) and not isinstance(predicate, Tag): - raise Exception(f"must be callable or tag: {predicate!r}") - tags = self.get_tags() - if not tags: - return False - if predicate in tags: - return True - if not callable(predicate): - return False - return predicate(tags) - - def _match_line(line, tag, current_tags): assert all(isinstance(_, Tag) for _ in current_tags), repr(current_tags) if tag in current_tags: @@ -413,12 +229,10 @@ def activate( .. container:: example - Writes (deactivated) tag with ``"%@%"`` prefix into LilyPond - input: + Writes (deactivated) tag with ``"%@%"`` prefix into LilyPond input: >>> staff = abjad.Staff("c'4 d' e' f'") - >>> string = r"\markup { \with-color #red Allegro }" - >>> markup = abjad.Markup(string) + >>> markup = abjad.Markup(r"\markup { \with-color #red Allegro }") >>> abjad.attach( ... markup, ... staff[0], @@ -449,7 +263,7 @@ def activate( { c'4 %! RED_MARKUP - - \markup { \with-color #red Allegro } %@% + - \markup { \with-color #red Allegro } %@% d'4 e'4 f'4 @@ -487,7 +301,7 @@ def activate( { c'4 %! RED_MARKUP - - \markup { \with-color #red Allegro } %@% + - \markup { \with-color #red Allegro } %@% d'4 e'4 f'4 @@ -530,9 +344,10 @@ def activate( index = first_nonwhitespace_index if line[index : index + 4] in ("%%% ", "%@% "): if "%@% " in line: - line = line.replace("%@%", " ") + line = line.replace("%@% ", "") suffix = " %@%" else: + # TODO: replace with "" instead of " "? line = line.replace("%%%", " ") suffix = None assert line.endswith("\n"), repr(line) diff --git a/tests/test_class_design.py b/tests/test_class_design.py index b6784692460..40e7c69f75d 100644 --- a/tests/test_class_design.py +++ b/tests/test_class_design.py @@ -18,7 +18,6 @@ abjad.Articulation: ("staccato",), abjad.Bundle: (abjad.Articulation("."),), abjad.ColorFingering: (0,), - abjad.Line: ("text",), abjad.Markup: (r"\markup Allegro",), abjad.MetricModulation: (abjad.Note("c'4"), abjad.Note("c'4.")), abjad.MetronomeMark: ((1, 4), 90), @@ -85,7 +84,6 @@ def test_abjad___repr___01(class_): """ _allowed_to_be_empty_string = ( abjad.Accidental, - abjad.Line, abjad.Tag, ) if inspect.isabstract(class_): From d54227929465f18a350bee8f891516f5b29f1ee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trevor=20Ba=C4=8Da?= Date: Sat, 10 Sep 2022 11:17:54 -0400 Subject: [PATCH 2/3] Bumped to Abjad 3.11. --- README.rst | 8 ++++---- abjad/_version.py | 2 +- docs/source/conf.py | 2 +- setup.py | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index a9ee0ecd3d9..dd5e4d03ce0 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -Abjad 3.10 +Abjad 3.11 ========== Abjad helps composers build up complex pieces of music notation in iterative and @@ -31,7 +31,7 @@ Abjad requires Python 3.10 or later: .. code-block:: ~$ python --version - Python 3.10.2 + Python 3.10.5 Abjad requires LilyPond 2.23.6 or later. @@ -42,7 +42,7 @@ Make sure LilyPond is callable from the commandline: .. code-block:: $ lilypond --version - GNU LilyPond 2.23.9 + GNU LilyPond 2.23.12 Copyright (c) 1996--2022 by Han-Wen Nienhuys @@ -67,7 +67,7 @@ Start Python, import Abjad, start making music notation: .. code-block:: ~$ python - Python 3.10.2 (v3.10.2:a58ebcc701, Jan 13 2022, 14:50:16) + Python 3.10.5 (v3.10.5:f377153967, Jun 6 2022, 12:36:10) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import abjad diff --git a/abjad/_version.py b/abjad/_version.py index 6856eb1ae82..8984b4511e0 100644 --- a/abjad/_version.py +++ b/abjad/_version.py @@ -1,3 +1,3 @@ -__version_info__ = (3, 10) +__version_info__ = (3, 11) __version__ = ".".join(str(_) for _ in __version_info__[:2]) __version__ += "".join(__version_info__[2:]) diff --git a/docs/source/conf.py b/docs/source/conf.py index 70206063a67..09d1049b99a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -39,7 +39,7 @@ # navigation_depth=1 makes sidebar completely flat; # leave flat navigation in place forever: "navigation_depth": 1, - "style_nav_header_background": "#4488cc", + "style_nav_header_background": "#55aaff", } html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] project = "Abjad" diff --git a/setup.py b/setup.py index 95c74b7e8ed..eb04beeeaa2 100755 --- a/setup.py +++ b/setup.py @@ -60,8 +60,8 @@ ] extras_require = { - "nauert": ["abjad-ext-nauert>=3.10"], - "rmakers": ["abjad-ext-rmakers>=3.10"], + "nauert": ["abjad-ext-nauert>=3.11"], + "rmakers": ["abjad-ext-rmakers>=3.11"], } keywords = [ @@ -74,7 +74,7 @@ "black>=22.1.0", "flake8>=4.0.1", "isort>=5.10.1", - "mypy>=0.960", + "mypy>=0.971", "ply>=3.11", "pytest>=6.2.5", "pytest-cov>=3.0.0", @@ -83,7 +83,7 @@ "roman>=1.4", "sphinx-autodoc-typehints>=1.16.0", "sphinx-rtd-theme>=1.0.0", - "uqbar>=0.5.9", + "uqbar>=0.6.3", ] if __name__ == "__main__": From 994ebbdb6a95eeeb8e3e8fe30d3e2ddc8864e83a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trevor=20Ba=C4=8Da?= Date: Sat, 10 Sep 2022 11:17:54 -0400 Subject: [PATCH 3/3] Bumped to Abjad 3.11.